├── .github
└── workflows
│ └── ci.yaml
├── .gitignore
├── .golangci.yml
├── LICENSE
├── Readme.md
├── cmd
└── coverbee
│ └── main.go
├── cover.go
├── examples
├── bpf-to-bpf
├── bpf-to-bpf-blocklist.json
├── bpf-to-bpf.c
├── bpf-to-bpf.html
├── bpf_endian.h
├── bpf_helper_defs.h
├── bpf_helpers.h
├── compile.sh
├── datain
└── demo.sh
├── go.mod
├── go.sum
├── instrumentation.go
├── parser.go
├── pkg
├── cparser
│ ├── ast.go
│ └── parser.go
└── verifierlog
│ ├── .gitignore
│ ├── verifierlog.go
│ └── verifierlog_test.go
├── testdata
└── .gitignore
└── tools
└── check-go-mod.sh
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | push:
4 | tags:
5 | - "v*"
6 | branches:
7 | - "master"
8 | pull_request:
9 | jobs:
10 | ci:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Install Go
14 | uses: actions/setup-go@v3
15 | with:
16 | go-version: 1.20.x
17 | - name: checkout code
18 | uses: actions/checkout@v2
19 | - name: Run linters
20 | uses: golangci/golangci-lint-action@v3
21 | with:
22 | version: v1.51.2
23 | args: --config=.golangci.yml
24 | - name: Install revive
25 | run: go install github.com/mgechev/revive@v1.1.3
26 | - name: Run revive
27 | run: revive -set_exit_status=1
28 | - name: Run Unit tests
29 | run: go test
30 | - name: Check go mod tidyness
31 | run: ./tools/check-go-mod.sh
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ./coverbee
2 | examples/coverbee
3 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | run:
2 | tests: false
3 |
4 | linters-settings:
5 | errcheck:
6 | # report about not checking of errors in type assertions: `a := b.(MyStruct)`;
7 | # default is false: such cases aren't reported by default.
8 | check-type-assertions: true
9 | # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
10 | # default is false: such cases aren't reported by default.
11 | check-blank: true
12 |
13 | govet:
14 | # report about shadowed variables
15 | check-shadowing: true
16 | # Enable all analyzers
17 | enable-all: true
18 |
19 | lll:
20 | # max line length, lines longer will be reported. Default is 120.
21 | # '\t' is counted as 1 character by default, and can be changed with the tab-width option
22 | line-length: 120
23 | # tab width in spaces. Default to 1.
24 | tab-width: 4
25 |
26 | misspell:
27 | # Correct spellings using locale preferences for US or UK.
28 | # Default is to use a neutral variety of English.
29 | # Setting locale to US will correct the British spelling of 'colour' to 'color'.
30 | locale: US
31 | ignore-words: []
32 |
33 | linters:
34 | # please, do not use `enable-all`: it's deprecated and will be removed soon.
35 | # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
36 | disable-all: true
37 | enable:
38 | - govet # Check for common errors
39 | - errcheck # Check for missing error handling
40 | - staticcheck # Adds extra checks on top of govet
41 | - gosimple # Check for code which can be simpeler
42 | - structcheck # Check for unused struct fields
43 | - varcheck # Check for unused globals and consts
44 | - ineffassign # Check for ineffectual assignments
45 | - deadcode # Check for dead/unreachable code
46 | - bodyclose # Check for unclosed HTTP bodies (causes resource leaking)
47 | - gofmt # Check for code formatting
48 | - gofumpt # Is stricter than gofmt
49 | - gosec # Inspects source code for security problems
50 | - unconvert # Remove unnecessary type conversions
51 | - misspell # Finds commonly misspelled English words in comments
52 | - lll # Reports long lines
53 | - revive # A maintained replacement for golint
54 | - depguard # Make sure we don't accidentally dependencies
55 |
56 | # DO NOT ENABLE
57 | # unused gives lots of false positives
58 | # - unused # Check for unused consts, variables, functions and types
59 | # golint is deprecated
60 | # - golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes
61 | issues:
62 | exclude-rules:
63 | # fieldalignment is an optimization which often is traded of for readability of a struct
64 | - text: "fieldalignment:"
65 | linters:
66 | - govet
67 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} Authors of Coverbee
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # CoverBee
2 | [](https://pkg.go.dev/github.com/cilium/coverbee)
3 |
4 | Code coverage collection tool for eBPF programs. CoverBee can instrument already compiled eBPF programs by giving it
5 | an ELF file. This allows for coverage testing without modifying the existing toolchain.
6 |
7 | ## Installation
8 |
9 | `go install github.com/cilium/coverbee/cmd/coverbee@latest`
10 |
11 | ## Usage CLI
12 |
13 | First, instrument and load the programs in a ELF file by using `coverbee load`.
14 | All programs will be pinned in the directory specified by `--prog-pin-dir`.
15 | If `--map-pin-dir` is specified, all maps with with `pinning = LIBBPF_PIN_BY_NAME` set will be pinned in the given map.
16 | If `--map-pin-dir` is not specified, a pin location for the cover-map must be specified with `--covermap-pin`.
17 | The block-list will be written as JSON to a location specified by `--block-list` this file contains translation data
18 | and must be passed to `coverbee cover` afterwards.
19 |
20 | ```
21 | Instrument all programs in the given ELF file and load them into the kernel
22 |
23 | Usage:
24 | coverbee load {--elf=ELF path} {--prog-pin-dir=path to dir} {--map-pin-dir=path to dir | --covermap-pin=path to covermap} {--block-list=path to blocklist} [flags]
25 |
26 | Flags:
27 | --block-list string Path where the block-list is stored (contains coverage data to source code mapping, needed when reading from cover map)
28 | --covermap-pin string Path to pin for the covermap (created by coverbee containing coverage information)
29 | --elf string Path to the ELF file containing the programs
30 | -h, --help help for load
31 | --log string Path for ultra-verbose log output
32 | --map-pin-dir string Path to the directory containing map pins
33 | --prog-pin-dir string Path the directory where the loaded programs will be pinned
34 | --prog-type string Explicitly set the program type
35 | ```
36 |
37 | Then attach the programs or test them with `BPF_TEST_RUN`.
38 |
39 | Once done, to inspect the coverage call `coverbee cover`, pass it the same `--map-pin-dir`/`--covermap-pin` and
40 | `--block-list` as was used for `coverbee load`. Specify a path for the output with `--output` which is html by default
41 | but can also be set to output go-cover for use with other tools by setting `--format go-cover`
42 |
43 | ```
44 | Collect coverage data and output to file
45 |
46 | Usage:
47 | coverbee cover {--map-pin-dir=path to dir | --covermap-pin=path to covermap} {--block-list=path to blocklist} {--output=path to report output} [flags]
48 |
49 | Flags:
50 | --block-list string Path where the block-list is stored (contains coverage data to source code mapping, needed when reading from cover map)
51 | --covermap-pin string Path to pin for the covermap (created by coverbee containing coverage information)
52 | --format string Output format (options: html, go-cover) (default "html")
53 | -h, --help help for cover
54 | --map-pin-dir string Path to the directory containing map pins
55 | --output string Path to the coverage output
56 | ```
57 |
58 | Don't forget to clean up the programs by detaching and/or removing the pins.
59 |
60 | ## Usage as library
61 |
62 | 1. Load the ELF file using `cilium/ebpf`
63 | 2. Perform normal setup(except for loading the programs, maps can be pre-loaded)
64 | 3. Call `coverbee.InstrumentAndLoadCollection` instead of using `ebpf.NewCollectionWithOptions`
65 | 4. Attach the program or run tests
66 | 5. Convert the CFG gotten in step 3 to a block-list with `coverbee.CFGToBlockList`
67 | 6. Get the `coverbee_covermap` from the collection and apply its contents to the block-list
68 | with `coverbee.ApplyCoverMapToBlockList`
69 | 7. Convert the block-list into a go-cover or HTML report file with `coverbee.BlockListToGoCover` or
70 | `coverbee.BlockListToHTML` respectively
71 |
72 | ## How does CoverBee work
73 |
74 | CoverBee instruments existing compiled eBPF programs in ELF format and load them into the kernel. This instrumentation
75 | will increment numbers in a eBPF map when certain parts of the program are ran. CoverBee uses the kernel verifier logs
76 | to find out which registers and stack slots are not used by the program, and uses these for the instrumentation code.
77 |
78 | The contents of the cover-map are be mapped back to the source file via the block-list. This block-list is constructed
79 | from the control flow graph of the programs and the BTF.ext line information. Then a modified version of `go tool cover`
80 | is used to create HTML reports.
81 |
82 | ## Limitations / Requirements
83 |
84 | * CoverBee requires up to 3 stack slots (24 bytes) available on the stack, programs close to the limit might not pass
85 | the verifier once instrumented.
86 | * CoverBee adds instructions to the programs, programs close to the instruction or complexity limit of the kernel might
87 | not pass the verifier once instrumented.
88 | * CoverBee used BTF.ext information to convert instructions to coverage information, ELF files without BTF will not work
89 | * CoverBee requires the source code of the programs to pre present at the same location as at compile time and to
90 | contain the same contents. BTF.ext contains line and column offsets to absolute filepaths, changes in path or file
91 | contents between compilation and coverage testing might result in invalid or non-working coverage reports.
92 | * CoverBee will add a map named `coverbee_covermap` to the collection, so this name can't be used by the program itself.
93 |
--------------------------------------------------------------------------------
/cmd/coverbee/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "io"
9 | "os"
10 | "path/filepath"
11 | "sort"
12 | "strings"
13 |
14 | "github.com/cilium/coverbee"
15 | "github.com/cilium/ebpf"
16 | "github.com/spf13/cobra"
17 | )
18 |
19 | var root = &cobra.Command{
20 | Use: "coverbee",
21 | // TODO pimp output
22 | }
23 |
24 | func main() {
25 | root.AddCommand(
26 | loadCmd(),
27 | coverageCmd(),
28 | )
29 |
30 | if err := root.Execute(); err != nil {
31 | fmt.Fprintln(os.Stderr, err)
32 | os.Exit(1)
33 | }
34 | }
35 |
36 | var (
37 | flagMapPinDir string
38 | flagCoverMapPinPath string
39 | flagBlockListPath string
40 | )
41 |
42 | var (
43 | flagElfPath string
44 | flagProgPinDir string
45 | flagProgType string
46 | flagLogPath string
47 |
48 | flagDisableInterpolation bool
49 | flagForceInterpolation bool
50 | )
51 |
52 | func panicOnError(err error) {
53 | if err != nil {
54 | panic(err)
55 | }
56 | }
57 |
58 | func loadCmd() *cobra.Command {
59 | load := &cobra.Command{
60 | Use: "load {--elf=ELF path} {--prog-pin-dir=path to dir} " +
61 | "{--map-pin-dir=path to dir | --covermap-pin=path to covermap} " +
62 | "{--block-list=path to blocklist}",
63 | Short: "Instrument all programs in the given ELF file and load them into the kernel",
64 | // Long: "",
65 | RunE: load,
66 | }
67 |
68 | fs := load.Flags()
69 |
70 | fs.StringVar(&flagElfPath, "elf", "", "Path to the ELF file containing the programs")
71 | panicOnError(load.MarkFlagFilename("elf", "o", "elf"))
72 | panicOnError(load.MarkFlagRequired("elf"))
73 |
74 | fs.StringVar(&flagProgPinDir, "prog-pin-dir", "", "Path the directory where the loaded programs will be pinned")
75 | panicOnError(load.MarkFlagDirname("prog-pin-dir"))
76 | panicOnError(load.MarkFlagRequired("prog-pin-dir"))
77 |
78 | fs.StringVar(&flagProgType, "prog-type", "", "Explicitly set the program type")
79 |
80 | fs.StringVar(&flagMapPinDir, "map-pin-dir", "", "Path to the directory containing map pins")
81 | panicOnError(load.MarkFlagDirname("map-pin-dir"))
82 |
83 | fs.StringVar(&flagCoverMapPinPath, "covermap-pin", "", "Path to pin for the covermap (created by coverbee "+
84 | "containing coverage information)")
85 | panicOnError(load.MarkFlagFilename("covermap-pin"))
86 |
87 | fs.StringVar(&flagBlockListPath, "block-list", "", "Path where the block-list is stored (contains coverage data "+
88 | "to source code mapping, needed when reading from cover map)")
89 | panicOnError(load.MarkFlagFilename("block-list", "json"))
90 | panicOnError(load.MarkFlagRequired("block-list"))
91 |
92 | fs.StringVar(&flagLogPath, "log", "", "Path for ultra-verbose log output")
93 |
94 | return load
95 | }
96 |
97 | func checkCovermapFlags(cmd *cobra.Command, args []string) error {
98 | if flagMapPinDir == "" && flagCoverMapPinPath == "" {
99 | return fmt.Errorf("either --map-pin-dir or --covermap-pin must be set")
100 | }
101 |
102 | if flagMapPinDir != "" && flagCoverMapPinPath != "" {
103 | return fmt.Errorf("either --map-pin-dir or --covermap-pin must be set, not both")
104 | }
105 |
106 | return nil
107 | }
108 |
109 | func load(cmd *cobra.Command, args []string) error {
110 | if err := checkCovermapFlags(cmd, args); err != nil {
111 | return err
112 | }
113 |
114 | spec, err := ebpf.LoadCollectionSpec(flagElfPath)
115 | if err != nil {
116 | return fmt.Errorf("Load collection spec: %w", err)
117 | }
118 |
119 | if flagProgType != "" {
120 | progTestType := strToProgType[flagProgType]
121 | if progTestType == ebpf.UnspecifiedProgram {
122 | options := make([]string, 0, len(strToProgType))
123 | for option := range strToProgType {
124 | options = append(options, option)
125 | }
126 | sort.Strings(options)
127 |
128 | var sb strings.Builder
129 | fmt.Fprintf(&sb, "Invalid --prog-type value '%s', pick from:\n", flagProgType)
130 | for _, option := range options {
131 | fmt.Fprintf(&sb, " - %s\n", option)
132 | }
133 |
134 | return errors.New(sb.String())
135 | }
136 |
137 | // Set all unknown program types to the specified type
138 | for _, spec := range spec.Programs {
139 | if spec.Type == ebpf.UnspecifiedProgram {
140 | spec.Type = progTestType
141 | }
142 | }
143 | }
144 |
145 | for _, spec := range spec.Programs {
146 | if spec.Type == ebpf.UnspecifiedProgram {
147 | return fmt.Errorf(
148 | "Program '%s' is of an unspecified type, use --prog-type to explicitly set one",
149 | spec.Name,
150 | )
151 | }
152 | }
153 |
154 | for _, m := range spec.Maps {
155 | if m.Extra != nil {
156 | //nolint:errcheck // we explicitly discard the error, no remediation available
157 | _, _ = io.ReadAll(m.Extra)
158 | }
159 | }
160 |
161 | opts := ebpf.CollectionOptions{}
162 |
163 | if flagMapPinDir != "" {
164 | opts.Maps.PinPath = flagMapPinDir
165 | }
166 |
167 | var logWriter io.Writer
168 | if flagLogPath != "" {
169 | var logFile *os.File
170 | logFile, err = os.Create(flagLogPath)
171 | if err != nil {
172 | return fmt.Errorf("open log file: %w", err)
173 | }
174 | defer logFile.Close()
175 |
176 | logBuf := bufio.NewWriter(logFile)
177 | defer logBuf.Flush()
178 |
179 | logWriter = logBuf
180 | }
181 |
182 | coll, cfg, err := coverbee.InstrumentAndLoadCollection(spec, opts, logWriter)
183 | if err != nil {
184 | return fmt.Errorf("error while instrumenting and loading program: %w", err)
185 | }
186 | defer coll.Close()
187 |
188 | for name, prog := range coll.Programs {
189 | if err = prog.Pin(filepath.Join(flagProgPinDir, name)); err != nil {
190 | return fmt.Errorf("error pinning program '%s': %w", name, err)
191 | }
192 | }
193 |
194 | if flagMapPinDir != "" {
195 | if err = coll.Maps["coverbee_covermap"].Pin(filepath.Join(flagMapPinDir, "coverbee_covermap")); err != nil {
196 | return fmt.Errorf("error pinning covermap: %w", err)
197 | }
198 | }
199 |
200 | if flagCoverMapPinPath != "" {
201 | if err = coll.Maps["coverbee_covermap"].Pin(flagCoverMapPinPath); err != nil {
202 | return fmt.Errorf("error pinning covermap: %w", err)
203 | }
204 | }
205 |
206 | blockList := coverbee.CFGToBlockList(cfg)
207 |
208 | blockListFile, err := os.Create(flagBlockListPath)
209 | if err != nil {
210 | return fmt.Errorf("error create block-list: %w", err)
211 | }
212 | defer blockListFile.Close()
213 |
214 | if err = json.NewEncoder(blockListFile).Encode(&blockList); err != nil {
215 | return fmt.Errorf("error encoding block-list: %w", err)
216 | }
217 |
218 | fmt.Println("Programs instrumented and loaded")
219 |
220 | return nil
221 | }
222 |
223 | var (
224 | flagOutputFormat string
225 | flagOutputPath string
226 | )
227 |
228 | func coverageCmd() *cobra.Command {
229 | coverCmd := &cobra.Command{
230 | Use: "cover",
231 | Short: "Collect coverage data and output to file",
232 | RunE: coverage,
233 | }
234 |
235 | fs := coverCmd.Flags()
236 |
237 | fs.StringVar(&flagMapPinDir, "map-pin-dir", "", "Path to the directory containing map pins")
238 | panicOnError(coverCmd.MarkFlagDirname("map-pin-dir"))
239 |
240 | fs.StringVar(&flagCoverMapPinPath, "covermap-pin", "", "Path to pin for the covermap (created by coverbee "+
241 | "containing coverage information)")
242 | panicOnError(coverCmd.MarkFlagFilename("covermap-pin"))
243 |
244 | fs.StringVar(&flagBlockListPath, "block-list", "", "Path where the block-list is stored (contains coverage data "+
245 | "to source code mapping, needed when reading from cover map)")
246 | panicOnError(coverCmd.MarkFlagFilename("block-list", "json"))
247 | panicOnError(coverCmd.MarkFlagRequired("block-list"))
248 |
249 | fs.StringVar(&flagOutputFormat, "format", "html", "Output format (options: html, go-cover)")
250 |
251 | fs.StringVar(&flagOutputPath, "output", "", "Path to the coverage output")
252 | panicOnError(coverCmd.MarkFlagRequired("output"))
253 |
254 | fs.BoolVar(&flagDisableInterpolation, "disable-interpolation", false, "Disable source based interpolation")
255 | fs.BoolVar(&flagForceInterpolation, "force-interpolation", false, "Force source based interpolation, or error")
256 |
257 | return coverCmd
258 | }
259 |
260 | func coverage(cmd *cobra.Command, args []string) error {
261 | if err := checkCovermapFlags(cmd, args); err != nil {
262 | return err
263 | }
264 |
265 | var (
266 | coverMap *ebpf.Map
267 | err error
268 | )
269 | if flagMapPinDir != "" {
270 | coverMap, err = ebpf.LoadPinnedMap(filepath.Join(flagMapPinDir, "coverbee_covermap"), nil)
271 | if err != nil {
272 | return fmt.Errorf("load covermap pin: %w", err)
273 | }
274 | } else {
275 | coverMap, err = ebpf.LoadPinnedMap(flagCoverMapPinPath, nil)
276 | if err != nil {
277 | return fmt.Errorf("load covermap pin: %w", err)
278 | }
279 | }
280 |
281 | blockList := make([][]coverbee.CoverBlock, 0)
282 |
283 | blockListPath, err := os.Open(flagBlockListPath)
284 | if err != nil {
285 | return fmt.Errorf("open block-list: %w", err)
286 | }
287 |
288 | if err = json.NewDecoder(blockListPath).Decode(&blockList); err != nil {
289 | return fmt.Errorf("decode block-list: %w", err)
290 | }
291 |
292 | if err = coverbee.ApplyCoverMapToBlockList(coverMap, blockList); err != nil {
293 | return fmt.Errorf("apply covermap: %w", err)
294 | }
295 |
296 | outBlocks := blockList
297 | if !flagDisableInterpolation {
298 | outBlocks, err = coverbee.SourceCodeInterpolation(blockList, nil)
299 | if err != nil {
300 | if flagForceInterpolation {
301 | return fmt.Errorf("error while interpolating using source files: %w", err)
302 | }
303 |
304 | fmt.Printf("Warning error while interpolating using source files, falling back: %s", err.Error())
305 | outBlocks = blockList
306 | }
307 | }
308 |
309 | var output io.Writer
310 | if flagOutputPath == "-" {
311 | output = os.Stdout
312 | } else {
313 | var f *os.File
314 | f, err = os.Create(flagOutputPath)
315 | if err != nil {
316 | return fmt.Errorf("error creating output file: %w", err)
317 | }
318 | output = f
319 | defer f.Close()
320 | }
321 |
322 | switch flagOutputFormat {
323 | case "html":
324 | if err = coverbee.BlockListToHTML(outBlocks, output, "count"); err != nil {
325 | return fmt.Errorf("block list to HTML: %w", err)
326 | }
327 | case "go-cover", "cover":
328 | coverbee.BlockListToGoCover(outBlocks, output, "count")
329 | default:
330 | return fmt.Errorf("unknown output format")
331 | }
332 |
333 | return nil
334 | }
335 |
336 | var strToProgType = map[string]ebpf.ProgramType{
337 | "socket": ebpf.SocketFilter,
338 | "sk_reuseport/migrate": ebpf.SkReuseport,
339 | "sk_reuseport": ebpf.SkReuseport,
340 | "kprobe": ebpf.Kprobe,
341 | "uprobe": ebpf.Kprobe,
342 | "kretprobe": ebpf.Kprobe,
343 | "uretprobe": ebpf.Kprobe,
344 | "tc": ebpf.SchedCLS,
345 | "classifier": ebpf.SchedCLS,
346 | "action": ebpf.SchedACT,
347 | "tracepoint": ebpf.TracePoint,
348 | "tp": ebpf.TracePoint,
349 | "raw_tracepoint": ebpf.RawTracepoint,
350 | "raw_tp": ebpf.RawTracepoint,
351 | "raw_tracepoint.w": ebpf.RawTracepointWritable,
352 | "raw_tp.w": ebpf.RawTracepointWritable,
353 | "tp_btf": ebpf.Tracing,
354 | "fentry": ebpf.Tracing,
355 | "fmod_ret": ebpf.Tracing,
356 | "fexit": ebpf.Tracing,
357 | "fentry.s": ebpf.Tracing,
358 | "fmod_ret.s": ebpf.Tracing,
359 | "fexit.s": ebpf.Tracing,
360 | "freplace": ebpf.Extension,
361 | "lsm": ebpf.LSM,
362 | "lsm.s": ebpf.LSM,
363 | "iter": ebpf.Tracing,
364 | "syscall": ebpf.Syscall,
365 | "xdp_devmap": ebpf.XDP,
366 | "xdp_cpumap": ebpf.XDP,
367 | "xdp": ebpf.XDP,
368 | "perf_event": ebpf.PerfEvent,
369 | "lwt_in": ebpf.LWTIn,
370 | "lwt_out": ebpf.LWTOut,
371 | "lwt_xmit": ebpf.LWTXmit,
372 | "lwt_seg6local": ebpf.LWTSeg6Local,
373 | "cgroup_skb/ingress": ebpf.CGroupSKB,
374 | "cgroup_skb/egress": ebpf.CGroupSKB,
375 | "cgroup/skb": ebpf.CGroupSKB,
376 | "cgroup/sock_create": ebpf.CGroupSock,
377 | "cgroup/sock_release": ebpf.CGroupSock,
378 | "cgroup/sock": ebpf.CGroupSock,
379 | "cgroup/post_bind4": ebpf.CGroupSock,
380 | "cgroup/post_bind6": ebpf.CGroupSock,
381 | "cgroup/dev": ebpf.CGroupDevice,
382 | "sockops": ebpf.SockOps,
383 | "sk_skb/stream_parser": ebpf.SkSKB,
384 | "sk_skb/stream_verdict": ebpf.SkSKB,
385 | "sk_skb": ebpf.SkSKB,
386 | "sk_msg": ebpf.SkMsg,
387 | "lirc_mode2": ebpf.LircMode2,
388 | "flow_dissector": ebpf.FlowDissector,
389 | "cgroup/bind4": ebpf.CGroupSockAddr,
390 | "cgroup/bind6": ebpf.CGroupSockAddr,
391 | "cgroup/connect4": ebpf.CGroupSockAddr,
392 | "cgroup/connect6": ebpf.CGroupSockAddr,
393 | "cgroup/sendmsg4": ebpf.CGroupSockAddr,
394 | "cgroup/sendmsg6": ebpf.CGroupSockAddr,
395 | "cgroup/recvmsg4": ebpf.CGroupSockAddr,
396 | "cgroup/recvmsg6": ebpf.CGroupSockAddr,
397 | "cgroup/getpeername4": ebpf.CGroupSockAddr,
398 | "cgroup/getpeername6": ebpf.CGroupSockAddr,
399 | "cgroup/getsockname4": ebpf.CGroupSockAddr,
400 | "cgroup/getsockname6": ebpf.CGroupSockAddr,
401 | "cgroup/sysctl": ebpf.CGroupSysctl,
402 | "cgroup/getsockopt": ebpf.CGroupSockopt,
403 | "cgroup/setsockopt": ebpf.CGroupSockopt,
404 | "struct_ops": ebpf.StructOps,
405 | "sk_lookup": ebpf.SkLookup,
406 | "seccomp": ebpf.SocketFilter,
407 | }
408 |
--------------------------------------------------------------------------------
/cover.go:
--------------------------------------------------------------------------------
1 | // Copyright 2013 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found at https://raw.githubusercontent.com/golang/go/master/LICENSE.
4 |
5 | package coverbee
6 |
7 | import (
8 | "bufio"
9 | "bytes"
10 | "fmt"
11 | "html/template"
12 | "io"
13 | "math"
14 | "os"
15 | "sort"
16 | "strings"
17 |
18 | "golang.org/x/tools/cover"
19 | )
20 |
21 | // HTMLOutput generates an HTML page from profile data.
22 | // coverage report is written to the out writer.
23 | func HTMLOutput(profiles []*cover.Profile, out io.Writer) error {
24 | var d templateData
25 |
26 | for _, profile := range profiles {
27 | if profile.Mode == "set" {
28 | d.Set = true
29 | }
30 |
31 | src, err := os.ReadFile(profile.FileName)
32 | if err != nil {
33 | return fmt.Errorf("can't read %q: %v", profile.FileName, err)
34 | }
35 |
36 | var buf strings.Builder
37 | err = htmlGen(&buf, src, profile.Boundaries(src))
38 | if err != nil {
39 | return err
40 | }
41 | d.Files = append(d.Files, &templateFile{
42 | Name: profile.FileName,
43 | //#nosec G203 HTML escaping doesn't seem like an issue here
44 | Body: template.HTML(buf.String()),
45 | Coverage: percentCovered(profile),
46 | })
47 | }
48 |
49 | err := htmlTemplate.Execute(out, d)
50 | if err != nil {
51 | return err
52 | }
53 |
54 | return nil
55 | }
56 |
57 | // percentCovered returns, as a percentage, the fraction of the statements in
58 | // the profile covered by the test run.
59 | // In effect, it reports the coverage of a given source file.
60 | func percentCovered(p *cover.Profile) float64 {
61 | var total, covered int64
62 | for _, b := range p.Blocks {
63 | total += int64(b.NumStmt)
64 | if b.Count > 0 {
65 | covered += int64(b.NumStmt)
66 | }
67 | }
68 | if total == 0 {
69 | return 0
70 | }
71 | return float64(covered) / float64(total) * 100
72 | }
73 |
74 | // htmlGen generates an HTML coverage report with the provided filename,
75 | // source code, and tokens, and writes it to the given Writer.
76 | func htmlGen(w io.Writer, src []byte, boundaries []cover.Boundary) error {
77 | dst := bufio.NewWriter(w)
78 | for i := range src {
79 | for len(boundaries) > 0 && boundaries[0].Offset == i {
80 | b := boundaries[0]
81 | if b.Start {
82 | n := 0
83 | if b.Count > 0 {
84 | n = int(math.Floor(b.Norm*9)) + 1
85 | }
86 | fmt.Fprintf(dst, ``, n, b.Count)
87 | } else {
88 | //nolint:errcheck // no remediation available if writes were to fail
89 | _, _ = dst.WriteString(" ")
90 | }
91 | boundaries = boundaries[1:]
92 | }
93 |
94 | //nolint:errcheck // no remediation available if writes were to fail
95 | switch b := src[i]; b {
96 | case '>':
97 | _, _ = dst.WriteString(">")
98 | case '<':
99 | _, _ = dst.WriteString("<")
100 | case '&':
101 | _, _ = dst.WriteString("&")
102 | case '\t':
103 | _, _ = dst.WriteString(" ")
104 | default:
105 | _ = dst.WriteByte(b)
106 | }
107 | }
108 | return dst.Flush()
109 | }
110 |
111 | // rgb returns an rgb value for the specified coverage value
112 | // between 0 (no coverage) and 10 (max coverage).
113 | func rgb(n int) string {
114 | if n == 0 {
115 | return "rgb(192, 0, 0)" // Red
116 | }
117 | // Gradient from gray to green.
118 | r := 128 - 12*(n-1)
119 | g := 128 + 12*(n-1)
120 | b := 128 + 3*(n-1)
121 | return fmt.Sprintf("rgb(%v, %v, %v)", r, g, b)
122 | }
123 |
124 | // colors generates the CSS rules for coverage colors.
125 | func colors() template.CSS {
126 | var buf bytes.Buffer
127 | for i := 0; i < 11; i++ {
128 | fmt.Fprintf(&buf, ".cov%v { color: %v }\n", i, rgb(i))
129 | }
130 | return template.CSS(buf.String())
131 | }
132 |
133 | var htmlTemplate = template.Must(template.New("html").Funcs(template.FuncMap{
134 | "colors": colors,
135 | }).Parse(tmplHTML))
136 |
137 | type templateData struct {
138 | Files []*templateFile
139 | Set bool
140 | }
141 |
142 | // PackageName returns a name for the package being shown.
143 | // It does this by choosing the penultimate element of the path
144 | // name, so foo.bar/baz/foo.go chooses 'baz'. This is cheap
145 | // and easy, avoids parsing the Go file, and gets a better answer
146 | // for package main. It returns the empty string if there is
147 | // a problem.
148 | func (td templateData) PackageName() string {
149 | if len(td.Files) == 0 {
150 | return ""
151 | }
152 | fileName := td.Files[0].Name
153 | elems := strings.Split(fileName, "/") // Package path is always slash-separated.
154 | // Return the penultimate non-empty element.
155 | for i := len(elems) - 2; i >= 0; i-- {
156 | if elems[i] != "" {
157 | return elems[i]
158 | }
159 | }
160 | return ""
161 | }
162 |
163 | type templateFile struct {
164 | Name string
165 | Body template.HTML
166 | Coverage float64
167 | }
168 |
169 | const tmplHTML = `
170 |
171 |
172 |
173 |
174 | {{$pkg := .PackageName}}{{if $pkg}}{{$pkg}}: {{end}}Go Coverage Report
175 |
209 |
210 |
211 |
212 |
213 |
214 | {{range $i, $f := .Files}}
215 | {{$f.Name}} ({{printf "%.1f" $f.Coverage}}%)
216 | {{end}}
217 |
218 |
219 |
220 | not tracked
221 | {{if .Set}}
222 | not covered
223 | covered
224 | {{else}}
225 | no coverage
226 | low coverage
227 | *
228 | *
229 | *
230 | *
231 | *
232 | *
233 | *
234 | *
235 | high coverage
236 | {{end}}
237 |
238 |
239 |
240 | {{range $i, $f := .Files}}
241 |
{{$f.Body}}
242 | {{end}}
243 |
244 |
245 |
272 |
273 | `
274 |
275 | // CoverBlock wraps the ProfileBlock, adding a filename so each CoverBlock can be turned into a line on a go coverage
276 | // file.
277 | type CoverBlock struct {
278 | Filename string
279 | ProfileBlock cover.ProfileBlock
280 | }
281 |
282 | func (cb CoverBlock) String() string {
283 | return fmt.Sprintf(
284 | "%s:%d.%d,%d.%d %d %d",
285 | cb.Filename,
286 | cb.ProfileBlock.StartLine,
287 | cb.ProfileBlock.StartCol,
288 | cb.ProfileBlock.EndLine,
289 | cb.ProfileBlock.EndCol,
290 | cb.ProfileBlock.NumStmt,
291 | cb.ProfileBlock.Count,
292 | )
293 | }
294 |
295 | // BlockListToGoCover convert a block-list into a go-cover file which can be interpreted by `go tool cover`.
296 | // `mode` value can be `set` or `count`, see `go tool cover -h` for details.
297 | func BlockListToGoCover(blockList [][]CoverBlock, out io.Writer, mode string) {
298 | fmt.Fprintln(out, "mode:", mode)
299 | for _, coverBlock := range blockList {
300 | for _, line := range coverBlock {
301 | fmt.Fprintln(out, line)
302 | }
303 | }
304 | }
305 |
306 | // ProfilesToGoCover convert a profile list into a go-cover file which can be interpreted by `go tool cover`.
307 | // `mode` value can be `set` or `count`, see `go tool cover -h` for details.
308 | func ProfilesToGoCover(profiles []*cover.Profile, out io.Writer, mode string) {
309 | fmt.Fprintln(out, "mode:", mode)
310 | for _, profile := range profiles {
311 | for _, block := range profile.Blocks {
312 | fmt.Fprintf(out,
313 | "%s:%d.%d,%d.%d %d %d\n",
314 | profile.FileName,
315 | block.StartLine,
316 | block.StartCol,
317 | block.EndLine,
318 | block.EndCol,
319 | block.NumStmt,
320 | block.Count,
321 | )
322 | }
323 | }
324 | }
325 |
326 | // BlockListToHTML converts a block-list into a HTML coverage report.
327 | func BlockListToHTML(blockList [][]CoverBlock, out io.Writer, mode string) error {
328 | var buf bytes.Buffer
329 | BlockListToGoCover(blockList, &buf, mode)
330 | profiles, err := cover.ParseProfilesFromReader(&buf)
331 | if err != nil {
332 | return err
333 | }
334 |
335 | if err = HTMLOutput(profiles, out); err != nil {
336 | return fmt.Errorf("write html: %w", err)
337 | }
338 |
339 | return nil
340 | }
341 |
342 | // BlockListFilePaths returns a sorted and deduplicateed list of file paths included in the block list
343 | func BlockListFilePaths(blockList [][]CoverBlock) []string {
344 | var uniqueFiles []string
345 | for _, blocks := range blockList {
346 | for _, block := range blocks {
347 | i := sort.SearchStrings(uniqueFiles, block.Filename)
348 | if i < len(uniqueFiles) && uniqueFiles[i] == block.Filename {
349 | continue
350 | }
351 |
352 | // Insert sorted
353 | uniqueFiles = append(uniqueFiles, "")
354 | copy(uniqueFiles[i+1:], uniqueFiles[i:])
355 | uniqueFiles[i] = block.Filename
356 | }
357 | }
358 | return uniqueFiles
359 | }
360 |
361 | // Check for:
362 | // aaaaa
363 | //
364 | // bbbbb
365 | //
366 | // -----
367 | //
368 | // aaaa
369 | //
370 | // bbbb
371 | // -----
372 | //
373 | // aaa
374 | //
375 | // bbbbbbb
376 | // -----
377 | // aaaaaaa
378 | //
379 | // bbb
380 | func blocksOverlap(a, b cover.ProfileBlock) bool {
381 | return (blockLTE(a.StartLine, a.StartCol, b.EndLine, b.EndCol) &&
382 | blockGTE(a.EndLine, a.EndCol, b.EndLine, b.EndCol)) ||
383 | (blockLTE(a.StartLine, a.StartCol, b.StartLine, b.StartCol) &&
384 | blockGTE(a.EndLine, a.EndCol, b.StartLine, b.StartCol)) ||
385 | (blockGTE(a.StartLine, a.StartCol, b.StartLine, b.StartCol) &&
386 | blockLTE(a.EndLine, a.EndCol, b.EndLine, b.EndCol))
387 | }
388 |
389 | // a <= b
390 | func blockLTE(aLine, aCol, bLine, bCol int) bool {
391 | if aLine == bLine {
392 | return aCol <= bCol
393 | }
394 |
395 | return aLine < bLine
396 | }
397 |
398 | // a >= b
399 | func blockGTE(aLine, aCol, bLine, bCol int) bool {
400 | if aLine == bLine {
401 | return aCol >= bCol
402 | }
403 |
404 | return aLine > bLine
405 | }
406 |
--------------------------------------------------------------------------------
/examples/bpf-to-bpf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cilium/coverbee/664438750fcead99e490bed7b47d8dd7ab1902d2/examples/bpf-to-bpf
--------------------------------------------------------------------------------
/examples/bpf-to-bpf-blocklist.json:
--------------------------------------------------------------------------------
1 | [[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":193,"StartCol":2,"EndLine":193,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":195,"StartCol":2,"EndLine":195,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":196,"StartCol":2,"EndLine":196,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":202,"StartCol":2,"EndLine":202,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":202,"StartCol":2,"EndLine":202,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":208,"StartCol":2,"EndLine":208,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":211,"StartCol":2,"EndLine":211,"EndCol":2000,"NumStmt":1,"Count":0}}],[],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":217,"StartCol":2,"EndLine":217,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":217,"StartCol":2,"EndLine":217,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":222,"StartCol":2,"EndLine":222,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":225,"StartCol":2,"EndLine":225,"EndCol":2000,"NumStmt":1,"Count":0}}],[],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":227,"StartCol":2,"EndLine":227,"EndCol":2000,"NumStmt":1,"Count":0}}],[],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":231,"StartCol":2,"EndLine":231,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":235,"StartCol":2,"EndLine":235,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":102,"StartCol":2,"EndLine":102,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":105,"StartCol":2,"EndLine":105,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":109,"StartCol":2,"EndLine":109,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":109,"StartCol":2,"EndLine":109,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":0,"StartCol":2,"EndLine":0,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":114,"StartCol":2,"EndLine":114,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":116,"StartCol":2,"EndLine":116,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":118,"StartCol":2,"EndLine":118,"EndCol":2000,"NumStmt":1,"Count":0}}],[],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":121,"StartCol":2,"EndLine":121,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":124,"StartCol":2,"EndLine":124,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":124,"StartCol":2,"EndLine":124,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":129,"StartCol":2,"EndLine":129,"EndCol":2000,"NumStmt":1,"Count":0}}],[],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":135,"StartCol":2,"EndLine":135,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":138,"StartCol":2,"EndLine":138,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":138,"StartCol":2,"EndLine":138,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":143,"StartCol":2,"EndLine":143,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":145,"StartCol":2,"EndLine":145,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":34,"StartCol":2,"EndLine":34,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":0,"StartCol":2,"EndLine":0,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":38,"StartCol":2,"EndLine":38,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":39,"StartCol":2,"EndLine":39,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":42,"StartCol":2,"EndLine":42,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":46,"StartCol":2,"EndLine":46,"EndCol":2000,"NumStmt":1,"Count":0}}],[],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":50,"StartCol":2,"EndLine":50,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":51,"StartCol":2,"EndLine":51,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":53,"StartCol":2,"EndLine":53,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":78,"StartCol":2,"EndLine":78,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":82,"StartCol":2,"EndLine":82,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":82,"StartCol":2,"EndLine":82,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":0,"StartCol":2,"EndLine":0,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":84,"StartCol":2,"EndLine":84,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":85,"StartCol":2,"EndLine":85,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":88,"StartCol":2,"EndLine":88,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":93,"StartCol":2,"EndLine":93,"EndCol":2000,"NumStmt":1,"Count":0}}],[],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":97,"StartCol":2,"EndLine":97,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":98,"StartCol":2,"EndLine":98,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":100,"StartCol":2,"EndLine":100,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":55,"StartCol":2,"EndLine":55,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":59,"StartCol":2,"EndLine":59,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":59,"StartCol":2,"EndLine":59,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":0,"StartCol":2,"EndLine":0,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":61,"StartCol":2,"EndLine":61,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":62,"StartCol":2,"EndLine":62,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":65,"StartCol":2,"EndLine":65,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":69,"StartCol":2,"EndLine":69,"EndCol":2000,"NumStmt":1,"Count":0}}],[],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":73,"StartCol":2,"EndLine":73,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":74,"StartCol":2,"EndLine":74,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":76,"StartCol":2,"EndLine":76,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":147,"StartCol":2,"EndLine":147,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":150,"StartCol":2,"EndLine":150,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":154,"StartCol":2,"EndLine":154,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":154,"StartCol":2,"EndLine":154,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":0,"StartCol":2,"EndLine":0,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":159,"StartCol":2,"EndLine":159,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":161,"StartCol":2,"EndLine":161,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":163,"StartCol":2,"EndLine":163,"EndCol":2000,"NumStmt":1,"Count":0}}],[],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":166,"StartCol":2,"EndLine":166,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":169,"StartCol":2,"EndLine":169,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":169,"StartCol":2,"EndLine":169,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":174,"StartCol":2,"EndLine":174,"EndCol":2000,"NumStmt":1,"Count":0}}],[],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":180,"StartCol":2,"EndLine":180,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":183,"StartCol":2,"EndLine":183,"EndCol":2000,"NumStmt":1,"Count":0}},{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":183,"StartCol":2,"EndLine":183,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":188,"StartCol":2,"EndLine":188,"EndCol":2000,"NumStmt":1,"Count":0}}],[{"Filename":"/home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c","ProfileBlock":{"StartLine":190,"StartCol":2,"EndLine":190,"EndCol":2000,"NumStmt":1,"Count":0}}]]
2 |
--------------------------------------------------------------------------------
/examples/bpf-to-bpf.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include "bpf_endian.h"
11 | #include "bpf_helpers.h"
12 |
13 | struct traffic_stats
14 | {
15 | __u64 pkts;
16 | __u64 bytes;
17 | };
18 |
19 | // Stats on packets keyed by protocol number
20 | bpf_map(ip_proto_stats, LRU_PERCPU_HASH, __u8, struct traffic_stats, 16, BPF_F_NO_COMMON_LRU);
21 |
22 | // Stats on udp packets keyed by dest port
23 | bpf_map(udp_stats, LRU_PERCPU_HASH, __u16, struct traffic_stats, 128, BPF_F_NO_COMMON_LRU);
24 |
25 | // Stats on tcp packets keyed by dest port
26 | bpf_map(tcp_stats, LRU_PERCPU_HASH, __u16, struct traffic_stats, 128, BPF_F_NO_COMMON_LRU);
27 |
28 | struct vlan_hdr
29 | {
30 | __be16 h_vlan_TCI;
31 | __be16 h_vlan_encapsulated_proto;
32 | };
33 |
34 | static __noinline void inc_ip_proto(
35 | __u8 proto,
36 | __u64 framesize)
37 | {
38 | struct traffic_stats *stats_ptr = bpf_map_lookup_elem(&ip_proto_stats, &proto);
39 | if (stats_ptr == NULL)
40 | {
41 | // Make a new stats object
42 | struct traffic_stats stats = {
43 | .pkts = 1,
44 | .bytes = framesize,
45 | };
46 | bpf_map_update_elem(&ip_proto_stats, &proto, &stats, BPF_ANY);
47 | }
48 | else
49 | {
50 | stats_ptr->pkts++;
51 | stats_ptr->bytes += framesize;
52 | }
53 | }
54 |
55 | static __noinline void inc_tcp(
56 | struct tcphdr *tcphdr,
57 | __u64 framesize)
58 | {
59 | __le16 le_dest = bpf_ntohs(tcphdr->dest);
60 | // Get existing stats
61 | struct traffic_stats *stats_ptr = bpf_map_lookup_elem(&tcp_stats, &le_dest);
62 | if (stats_ptr == NULL)
63 | {
64 | // Make a new stats object
65 | struct traffic_stats stats = {
66 | .pkts = 1,
67 | .bytes = framesize,
68 | };
69 | bpf_map_update_elem(&tcp_stats, &le_dest, &stats, BPF_ANY);
70 | }
71 | else
72 | {
73 | stats_ptr->pkts++;
74 | stats_ptr->bytes += framesize;
75 | }
76 | }
77 |
78 | static __noinline void inc_udp(
79 | struct udphdr *udphdr,
80 | __u64 framesize)
81 | {
82 | __le16 le_dest = bpf_ntohs(udphdr->dest);
83 | // Get existing stats
84 | struct traffic_stats *stats_ptr = bpf_map_lookup_elem(&udp_stats, &le_dest);
85 | if (stats_ptr == NULL)
86 | {
87 | // Make a new stats object
88 | struct traffic_stats stats = {
89 | .pkts = 1,
90 | .bytes = framesize,
91 | };
92 |
93 | bpf_map_update_elem(&udp_stats, &le_dest, &stats, BPF_ANY);
94 | }
95 | else
96 | {
97 | stats_ptr->pkts++;
98 | stats_ptr->bytes += framesize;
99 | }
100 | }
101 |
102 | static __noinline void handle_ipv4(void *data, void *data_end, __u64 nh_off)
103 | {
104 | struct iphdr *iph = data + nh_off;
105 | nh_off += sizeof(struct iphdr);
106 | __u64 framesize = data_end - data;
107 |
108 | // Drop packets which don't have enough data to fit the IPv4 header
109 | if (data + nh_off > data_end)
110 | {
111 | return;
112 | }
113 |
114 | __u8 ipproto = iph->protocol;
115 |
116 | inc_ip_proto(ipproto, framesize);
117 |
118 | if (ipproto == IPPROTO_UDP)
119 | {
120 | struct udphdr *udphdr = data + nh_off;
121 | nh_off += sizeof(struct udphdr);
122 |
123 | // If there is not enough data to parse a UDP header, drop the packet
124 | if (data + nh_off > data_end)
125 | {
126 | return;
127 | }
128 |
129 | inc_udp(udphdr, framesize);
130 | }
131 |
132 | if (ipproto == IPPROTO_TCP)
133 | {
134 | struct tcphdr *tcphdr = data + nh_off;
135 | nh_off += sizeof(struct tcphdr);
136 |
137 | // If there is not enough data to parse a UDP header, drop the packet
138 | if (data + nh_off > data_end)
139 | {
140 | return;
141 | }
142 |
143 | inc_tcp(tcphdr, framesize);
144 | }
145 | }
146 |
147 | static __noinline void handle_ipv6(void *data, void *data_end, __u64 nh_off)
148 | {
149 | struct ipv6hdr *ip6h = data + nh_off;
150 | nh_off += sizeof(struct ipv6hdr);
151 | __u64 framesize = data_end - data;
152 |
153 | // Drop packets which don't have enough data to fit the IPv4 header
154 | if (data + nh_off > data_end)
155 | {
156 | return;
157 | }
158 |
159 | __u8 ipproto = ip6h->nexthdr;
160 |
161 | inc_ip_proto(ipproto, framesize);
162 |
163 | if (ipproto == IPPROTO_UDP)
164 | {
165 | struct udphdr *udphdr = data + nh_off;
166 | nh_off += sizeof(struct udphdr);
167 |
168 | // If there is not enough data to parse a UDP header, drop the packet
169 | if (data + nh_off > data_end)
170 | {
171 | return;
172 | }
173 |
174 | inc_udp(udphdr, framesize);
175 | }
176 |
177 | if (ipproto == IPPROTO_TCP)
178 | {
179 | struct tcphdr *tcphdr = data + nh_off;
180 | nh_off += sizeof(struct tcphdr);
181 |
182 | // If there is not enough data to parse a TCP header, drop the packet
183 | if (data + nh_off > data_end)
184 | {
185 | return;
186 | }
187 |
188 | inc_tcp(tcphdr, framesize);
189 | }
190 | }
191 |
192 | SEC("xdp/proto_stats")
193 | int firewall_prog(struct xdp_md *ctx)
194 | {
195 | void *data_end = (void *)(long)ctx->data_end;
196 | void *data = (void *)(long)ctx->data;
197 |
198 | // Offset to the next header
199 | __u64 nh_off = sizeof(struct ethhdr);
200 |
201 | // If we don't even have enough data to a ethernet frame header, drop the message
202 | if (data + nh_off > data_end)
203 | {
204 | return XDP_DROP;
205 | }
206 |
207 | struct ethhdr *eth = data;
208 | __be16 h_proto = eth->h_proto;
209 |
210 | // If the ethernet packet contains a IEEE 802.1Q or 802.1AD VLAN header
211 | if (h_proto == bpf_htons(ETH_P_8021Q) || h_proto == bpf_htons(ETH_P_8021AD))
212 | {
213 | struct vlan_hdr *vhdr = data + nh_off;
214 | nh_off += sizeof(struct vlan_hdr);
215 |
216 | // Drop packets which don't have enough data to fit the VLAN header
217 | if (data + nh_off > data_end)
218 | {
219 | return XDP_DROP;
220 | }
221 |
222 | h_proto = vhdr->h_vlan_encapsulated_proto;
223 | }
224 |
225 | if (h_proto == bpf_htons(ETH_P_IP))
226 | {
227 | handle_ipv4(data, data_end, nh_off);
228 | }
229 | else if (h_proto == bpf_htons(ETH_P_IPV6))
230 | {
231 | handle_ipv6(data, data_end, nh_off);
232 | }
233 |
234 | return XDP_PASS;
235 | }
236 |
237 | char _license[] SEC("license") = "GPL";
--------------------------------------------------------------------------------
/examples/bpf-to-bpf.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | examples: Go Coverage Report
7 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | /home/dylan/Documents/work/coverbee/examples/bpf-to-bpf.c (45.0%)
59 |
60 |
61 |
62 |
63 | not tracked
64 |
65 | no coverage
66 | low coverage
67 | *
68 | *
69 | *
70 | *
71 | *
72 | *
73 | *
74 | *
75 | high coverage
76 |
77 |
78 |
79 |
80 |
81 |
#include <stddef.h>
82 | #include <linux/bpf.h>
83 | #include <linux/in.h>
84 | #include <linux/if_ether.h>
85 | #include <linux/if_packet.h>
86 | #include <linux/ip.h>
87 | #include <linux/ipv6.h>
88 | #include <linux/tcp.h>
89 | #include <linux/udp.h>
90 | #include "bpf_endian.h"
91 | #include "bpf_helpers.h"
92 |
93 | struct traffic_stats
94 | {
95 | __u64 pkts;
96 | __u64 bytes;
97 | };
98 |
99 | // Stats on packets keyed by protocol number
100 | bpf_map(ip_proto_stats, LRU_PERCPU_HASH, __u8, struct traffic_stats, 16, BPF_F_NO_COMMON_LRU);
101 |
102 | // Stats on udp packets keyed by dest port
103 | bpf_map(udp_stats, LRU_PERCPU_HASH, __u16, struct traffic_stats, 128, BPF_F_NO_COMMON_LRU);
104 |
105 | // Stats on tcp packets keyed by dest port
106 | bpf_map(tcp_stats, LRU_PERCPU_HASH, __u16, struct traffic_stats, 128, BPF_F_NO_COMMON_LRU);
107 |
108 | struct vlan_hdr
109 | {
110 | __be16 h_vlan_TCI;
111 | __be16 h_vlan_encapsulated_proto;
112 | };
113 |
114 | static __noinline void inc_ip_proto(
115 | __u8 proto,
116 | __u64 framesize)
117 | {
118 | struct traffic_stats *stats_ptr = bpf_map_lookup_elem(&ip_proto_stats, &proto);
119 | if (stats_ptr == NULL)
120 | {
121 | // Make a new stats object
122 | struct traffic_stats stats = {
123 | .pkts = 1,
124 | .bytes = framesize,
125 | };
126 | bpf_map_update_elem(&ip_proto_stats, &proto, &stats, BPF_ANY);
127 | }
128 | else
129 | {
130 | stats_ptr->pkts++;
131 | stats_ptr->bytes += framesize;
132 | }
133 | }
134 |
135 | static __noinline void inc_tcp(
136 | struct tcphdr *tcphdr,
137 | __u64 framesize)
138 | {
139 | __le16 le_dest = bpf_ntohs(tcphdr->dest);
140 | // Get existing stats
141 | struct traffic_stats *stats_ptr = bpf_map_lookup_elem(&tcp_stats, &le_dest);
142 | if (stats_ptr == NULL)
143 | {
144 | // Make a new stats object
145 | struct traffic_stats stats = {
146 | .pkts = 1,
147 | .bytes = framesize,
148 | };
149 | bpf_map_update_elem(&tcp_stats, &le_dest, &stats, BPF_ANY);
150 | }
151 | else
152 | {
153 | stats_ptr->pkts++;
154 | stats_ptr->bytes += framesize;
155 | }
156 | }
157 |
158 | static __noinline void inc_udp(
159 | struct udphdr *udphdr,
160 | __u64 framesize)
161 | {
162 | __le16 le_dest = bpf_ntohs(udphdr->dest);
163 | // Get existing stats
164 | struct traffic_stats *stats_ptr = bpf_map_lookup_elem(&udp_stats, &le_dest);
165 | if (stats_ptr == NULL)
166 | {
167 | // Make a new stats object
168 | struct traffic_stats stats = {
169 | .pkts = 1,
170 | .bytes = framesize,
171 | };
172 |
173 | bpf_map_update_elem(&udp_stats, &le_dest, &stats, BPF_ANY);
174 | }
175 | else
176 | {
177 | stats_ptr->pkts++;
178 | stats_ptr->bytes += framesize;
179 | }
180 | }
181 |
182 | static __noinline void handle_ipv4(void *data, void *data_end, __u64 nh_off)
183 | {
184 | struct iphdr *iph = data + nh_off;
185 | nh_off += sizeof(struct iphdr);
186 | __u64 framesize = data_end - data;
187 |
188 | // Drop packets which don't have enough data to fit the IPv4 header
189 | if (data + nh_off > data_end)
190 | {
191 | return;
192 | }
193 |
194 | __u8 ipproto = iph->protocol;
195 |
196 | inc_ip_proto(ipproto, framesize);
197 |
198 | if (ipproto == IPPROTO_UDP)
199 | {
200 | struct udphdr *udphdr = data + nh_off;
201 | nh_off += sizeof(struct udphdr);
202 |
203 | // If there is not enough data to parse a UDP header, drop the packet
204 | if (data + nh_off > data_end)
205 | {
206 | return;
207 | }
208 |
209 | inc_udp(udphdr, framesize);
210 | }
211 |
212 | if (ipproto == IPPROTO_TCP)
213 | {
214 | struct tcphdr *tcphdr = data + nh_off;
215 | nh_off += sizeof(struct tcphdr);
216 |
217 | // If there is not enough data to parse a UDP header, drop the packet
218 | if (data + nh_off > data_end)
219 | {
220 | return;
221 | }
222 |
223 | inc_tcp(tcphdr, framesize);
224 | }
225 | }
226 |
227 | static __noinline void handle_ipv6(void *data, void *data_end, __u64 nh_off)
228 | {
229 | struct ipv6hdr *ip6h = data + nh_off;
230 | nh_off += sizeof(struct ipv6hdr);
231 | __u64 framesize = data_end - data;
232 |
233 | // Drop packets which don't have enough data to fit the IPv4 header
234 | if (data + nh_off > data_end)
235 | {
236 | return;
237 | }
238 |
239 | __u8 ipproto = ip6h->nexthdr;
240 |
241 | inc_ip_proto(ipproto, framesize);
242 |
243 | if (ipproto == IPPROTO_UDP)
244 | {
245 | struct udphdr *udphdr = data + nh_off;
246 | nh_off += sizeof(struct udphdr);
247 |
248 | // If there is not enough data to parse a UDP header, drop the packet
249 | if (data + nh_off > data_end)
250 | {
251 | return;
252 | }
253 |
254 | inc_udp(udphdr, framesize);
255 | }
256 |
257 | if (ipproto == IPPROTO_TCP)
258 | {
259 | struct tcphdr *tcphdr = data + nh_off;
260 | nh_off += sizeof(struct tcphdr);
261 |
262 | // If there is not enough data to parse a TCP header, drop the packet
263 | if (data + nh_off > data_end)
264 | {
265 | return;
266 | }
267 |
268 | inc_tcp(tcphdr, framesize);
269 | }
270 | }
271 |
272 | SEC("xdp/proto_stats")
273 | int firewall_prog(struct xdp_md *ctx)
274 | {
275 | void *data_end = (void *)(long)ctx->data_end;
276 | void *data = (void *)(long)ctx->data;
277 |
278 | // Offset to the next header
279 | __u64 nh_off = sizeof(struct ethhdr);
280 |
281 | // If we don't even have enough data to a ethernet frame header, drop the message
282 | if (data + nh_off > data_end)
283 | {
284 | return XDP_DROP;
285 | }
286 |
287 | struct ethhdr *eth = data;
288 | __be16 h_proto = eth->h_proto;
289 |
290 | // If the ethernet packet contains a IEEE 802.1Q or 802.1AD VLAN header
291 | if (h_proto == bpf_htons(ETH_P_8021Q) || h_proto == bpf_htons(ETH_P_8021AD))
292 | {
293 | struct vlan_hdr *vhdr = data + nh_off;
294 | nh_off += sizeof(struct vlan_hdr);
295 |
296 | // Drop packets which don't have enough data to fit the VLAN header
297 | if (data + nh_off > data_end)
298 | {
299 | return XDP_DROP;
300 | }
301 |
302 | h_proto = vhdr->h_vlan_encapsulated_proto;
303 | }
304 |
305 | if (h_proto == bpf_htons(ETH_P_IP))
306 | {
307 | handle_ipv4(data, data_end, nh_off);
308 | }
309 | else if (h_proto == bpf_htons(ETH_P_IPV6))
310 | {
311 | handle_ipv6(data, data_end, nh_off);
312 | }
313 |
314 | return XDP_PASS;
315 | }
316 |
317 | char _license[] SEC("license") = "GPL";
318 |
319 |
320 |
321 |
348 |
349 |
--------------------------------------------------------------------------------
/examples/bpf_endian.h:
--------------------------------------------------------------------------------
1 | /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
2 | #ifndef __BPF_ENDIAN__
3 | #define __BPF_ENDIAN__
4 |
5 | /*
6 | * Isolate byte #n and put it into byte #m, for __u##b type.
7 | * E.g., moving byte #6 (nnnnnnnn) into byte #1 (mmmmmmmm) for __u64:
8 | * 1) xxxxxxxx nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx
9 | * 2) nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx 00000000
10 | * 3) 00000000 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn
11 | * 4) 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn 00000000
12 | */
13 | #define ___bpf_mvb(x, b, n, m) ((__u##b)(x) << (b - (n + 1) * 8) >> (b - 8) << (m * 8))
14 |
15 | #define ___bpf_swab16(x) ((__u16)(___bpf_mvb(x, 16, 0, 1) | \
16 | ___bpf_mvb(x, 16, 1, 0)))
17 |
18 | #define ___bpf_swab32(x) ((__u32)(___bpf_mvb(x, 32, 0, 3) | \
19 | ___bpf_mvb(x, 32, 1, 2) | \
20 | ___bpf_mvb(x, 32, 2, 1) | \
21 | ___bpf_mvb(x, 32, 3, 0)))
22 |
23 | #define ___bpf_swab64(x) ((__u64)(___bpf_mvb(x, 64, 0, 7) | \
24 | ___bpf_mvb(x, 64, 1, 6) | \
25 | ___bpf_mvb(x, 64, 2, 5) | \
26 | ___bpf_mvb(x, 64, 3, 4) | \
27 | ___bpf_mvb(x, 64, 4, 3) | \
28 | ___bpf_mvb(x, 64, 5, 2) | \
29 | ___bpf_mvb(x, 64, 6, 1) | \
30 | ___bpf_mvb(x, 64, 7, 0)))
31 |
32 | /* LLVM's BPF target selects the endianness of the CPU
33 | * it compiles on, or the user specifies (bpfel/bpfeb),
34 | * respectively. The used __BYTE_ORDER__ is defined by
35 | * the compiler, we cannot rely on __BYTE_ORDER from
36 | * libc headers, since it doesn't reflect the actual
37 | * requested byte order.
38 | *
39 | * Note, LLVM's BPF target has different __builtin_bswapX()
40 | * semantics. It does map to BPF_ALU | BPF_END | BPF_TO_BE
41 | * in bpfel and bpfeb case, which means below, that we map
42 | * to cpu_to_be16(). We could use it unconditionally in BPF
43 | * case, but better not rely on it, so that this header here
44 | * can be used from application and BPF program side, which
45 | * use different targets.
46 | */
47 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
48 | #define __bpf_ntohs(x) __builtin_bswap16(x)
49 | #define __bpf_htons(x) __builtin_bswap16(x)
50 | #define __bpf_constant_ntohs(x) ___bpf_swab16(x)
51 | #define __bpf_constant_htons(x) ___bpf_swab16(x)
52 | #define __bpf_ntohl(x) __builtin_bswap32(x)
53 | #define __bpf_htonl(x) __builtin_bswap32(x)
54 | #define __bpf_constant_ntohl(x) ___bpf_swab32(x)
55 | #define __bpf_constant_htonl(x) ___bpf_swab32(x)
56 | #define __bpf_be64_to_cpu(x) __builtin_bswap64(x)
57 | #define __bpf_cpu_to_be64(x) __builtin_bswap64(x)
58 | #define __bpf_constant_be64_to_cpu(x) ___bpf_swab64(x)
59 | #define __bpf_constant_cpu_to_be64(x) ___bpf_swab64(x)
60 | #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
61 | #define __bpf_ntohs(x) (x)
62 | #define __bpf_htons(x) (x)
63 | #define __bpf_constant_ntohs(x) (x)
64 | #define __bpf_constant_htons(x) (x)
65 | #define __bpf_ntohl(x) (x)
66 | #define __bpf_htonl(x) (x)
67 | #define __bpf_constant_ntohl(x) (x)
68 | #define __bpf_constant_htonl(x) (x)
69 | #define __bpf_be64_to_cpu(x) (x)
70 | #define __bpf_cpu_to_be64(x) (x)
71 | #define __bpf_constant_be64_to_cpu(x) (x)
72 | #define __bpf_constant_cpu_to_be64(x) (x)
73 | #else
74 | #error "Fix your compiler's __BYTE_ORDER__?!"
75 | #endif
76 |
77 | #define bpf_htons(x) \
78 | (__builtin_constant_p(x) ? __bpf_constant_htons(x) : __bpf_htons(x))
79 | #define bpf_ntohs(x) \
80 | (__builtin_constant_p(x) ? __bpf_constant_ntohs(x) : __bpf_ntohs(x))
81 | #define bpf_htonl(x) \
82 | (__builtin_constant_p(x) ? __bpf_constant_htonl(x) : __bpf_htonl(x))
83 | #define bpf_ntohl(x) \
84 | (__builtin_constant_p(x) ? __bpf_constant_ntohl(x) : __bpf_ntohl(x))
85 | #define bpf_cpu_to_be64(x) \
86 | (__builtin_constant_p(x) ? __bpf_constant_cpu_to_be64(x) : __bpf_cpu_to_be64(x))
87 | #define bpf_be64_to_cpu(x) \
88 | (__builtin_constant_p(x) ? __bpf_constant_be64_to_cpu(x) : __bpf_be64_to_cpu(x))
89 |
90 | #endif /* __BPF_ENDIAN__ */
91 |
--------------------------------------------------------------------------------
/examples/bpf_helpers.h:
--------------------------------------------------------------------------------
1 | /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
2 | #ifndef __BPF_HELPERS__
3 | #define __BPF_HELPERS__
4 |
5 | /*
6 | * Note that bpf programs need to include either
7 | * vmlinux.h (auto-generated from BTF) or linux/types.h
8 | * in advance since bpf_helper_defs.h uses such types
9 | * as __u64.
10 | */
11 | #include "bpf_helper_defs.h"
12 |
13 | #define __uint(name, val) int(*name)[val]
14 | #define __type(name, val) typeof(val) *name
15 | #define __array(name, val) typeof(val) *name[]
16 |
17 | /*
18 | * Helper macro to place programs, maps, license in
19 | * different sections in elf_bpf file. Section names
20 | * are interpreted by libbpf depending on the context (BPF programs, BPF maps,
21 | * extern variables, etc).
22 | * To allow use of SEC() with externs (e.g., for extern .maps declarations),
23 | * make sure __attribute__((unused)) doesn't trigger compilation warning.
24 | */
25 | #define SEC(name) \
26 | _Pragma("GCC diagnostic push") \
27 | _Pragma("GCC diagnostic ignored \"-Wignored-attributes\"") \
28 | __attribute__((section(name), used)) \
29 | _Pragma("GCC diagnostic pop")
30 |
31 | /* Avoid 'linux/stddef.h' definition of '__always_inline'. */
32 | #undef __always_inline
33 | #define __always_inline inline __attribute__((always_inline))
34 |
35 | #ifndef __noinline
36 | #define __noinline __attribute__((noinline))
37 | #endif
38 | #ifndef __weak
39 | #define __weak __attribute__((weak))
40 | #endif
41 |
42 | /*
43 | * Use __hidden attribute to mark a non-static BPF subprogram effectively
44 | * static for BPF verifier's verification algorithm purposes, allowing more
45 | * extensive and permissive BPF verification process, taking into account
46 | * subprogram's caller context.
47 | */
48 | #define __hidden __attribute__((visibility("hidden")))
49 |
50 | /* When utilizing vmlinux.h with BPF CO-RE, user BPF programs can't include
51 | * any system-level headers (such as stddef.h, linux/version.h, etc), and
52 | * commonly-used macros like NULL and KERNEL_VERSION aren't available through
53 | * vmlinux.h. This just adds unnecessary hurdles and forces users to re-define
54 | * them on their own. So as a convenience, provide such definitions here.
55 | */
56 | #ifndef NULL
57 | #define NULL ((void *)0)
58 | #endif
59 |
60 | #ifndef KERNEL_VERSION
61 | #define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + ((c) > 255 ? 255 : (c)))
62 | #endif
63 |
64 | /*
65 | * Helper macros to manipulate data structures
66 | */
67 | #ifndef offsetof
68 | #define offsetof(TYPE, MEMBER) ((unsigned long)&((TYPE *)0)->MEMBER)
69 | #endif
70 | #ifndef container_of
71 | #define container_of(ptr, type, member) \
72 | ( \
73 | { \
74 | void *__mptr = (void *)(ptr); \
75 | ((type *)(__mptr - offsetof(type, member))); \
76 | })
77 | #endif
78 |
79 | /*
80 | * Helper macro to throw a compilation error if __bpf_unreachable() gets
81 | * built into the resulting code. This works given BPF back end does not
82 | * implement __builtin_trap(). This is useful to assert that certain paths
83 | * of the program code are never used and hence eliminated by the compiler.
84 | *
85 | * For example, consider a switch statement that covers known cases used by
86 | * the program. __bpf_unreachable() can then reside in the default case. If
87 | * the program gets extended such that a case is not covered in the switch
88 | * statement, then it will throw a build error due to the default case not
89 | * being compiled out.
90 | */
91 | #ifndef __bpf_unreachable
92 | #define __bpf_unreachable() __builtin_trap()
93 | #endif
94 |
95 | /*
96 | * Helper function to perform a tail call with a constant/immediate map slot.
97 | */
98 | #if __clang_major__ >= 8 && defined(__bpf__)
99 | static __always_inline void
100 | bpf_tail_call_static(void *ctx, const void *map, const __u32 slot)
101 | {
102 | if (!__builtin_constant_p(slot))
103 | __bpf_unreachable();
104 |
105 | /*
106 | * Provide a hard guarantee that LLVM won't optimize setting r2 (map
107 | * pointer) and r3 (constant map index) from _different paths_ ending
108 | * up at the _same_ call insn as otherwise we won't be able to use the
109 | * jmpq/nopl retpoline-free patching by the x86-64 JIT in the kernel
110 | * given they mismatch. See also d2e4c1e6c294 ("bpf: Constant map key
111 | * tracking for prog array pokes") for details on verifier tracking.
112 | *
113 | * Note on clobber list: we need to stay in-line with BPF calling
114 | * convention, so even if we don't end up using r0, r4, r5, we need
115 | * to mark them as clobber so that LLVM doesn't end up using them
116 | * before / after the call.
117 | */
118 | asm volatile("r1 = %[ctx]\n\t"
119 | "r2 = %[map]\n\t"
120 | "r3 = %[slot]\n\t"
121 | "call 12" ::[ctx] "r"(ctx),
122 | [map] "r"(map), [slot] "i"(slot)
123 | : "r0", "r1", "r2", "r3", "r4", "r5");
124 | }
125 | #endif
126 |
127 | /*
128 | * Helper structure used by eBPF C program
129 | * to describe BPF map attributes to libbpf loader
130 | */
131 | struct bpf_map_def
132 | {
133 | unsigned int type;
134 | unsigned int key_size;
135 | unsigned int value_size;
136 | unsigned int max_entries;
137 | unsigned int map_flags;
138 | } __attribute__((deprecated("use BTF-defined maps in .maps section")));
139 |
140 | /*
141 | * A helper structure used by eBPF C program to describe map attributes to
142 | * elf_bpf loader
143 | */
144 | struct bpf_map
145 | {
146 | unsigned int type;
147 | unsigned int key_size;
148 | unsigned int value_size;
149 | unsigned int max_entries;
150 | unsigned int map_flags;
151 | };
152 |
153 | #define bpf_map(name, _type, type_key, type_val, _max_entries, _map_flags) \
154 | struct bpf_map SEC("maps") name = { \
155 | .type = BPF_MAP_TYPE_##_type, \
156 | .key_size = sizeof(type_key), \
157 | .value_size = sizeof(type_val), \
158 | .max_entries = _max_entries, \
159 | .map_flags = _map_flags, \
160 | }; \
161 | struct ____btf_map_##name \
162 | { \
163 | type_key key; \
164 | type_val value; \
165 | }; \
166 | struct ____btf_map_##name __attribute__((section(".maps." #name), used)) \
167 | ____btf_map_##name = {}
168 |
169 | enum libbpf_pin_type
170 | {
171 | LIBBPF_PIN_NONE,
172 | /* PIN_BY_NAME: pin maps by name (in /sys/fs/bpf by default) */
173 | LIBBPF_PIN_BY_NAME,
174 | };
175 |
176 | enum libbpf_tristate
177 | {
178 | TRI_NO = 0,
179 | TRI_YES = 1,
180 | TRI_MODULE = 2,
181 | };
182 |
183 | #define __kconfig __attribute__((section(".kconfig")))
184 | #define __ksym __attribute__((section(".ksyms")))
185 |
186 | #ifndef ___bpf_concat
187 | #define ___bpf_concat(a, b) a##b
188 | #endif
189 | #ifndef ___bpf_apply
190 | #define ___bpf_apply(fn, n) ___bpf_concat(fn, n)
191 | #endif
192 | #ifndef ___bpf_nth
193 | #define ___bpf_nth(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, N, ...) N
194 | #endif
195 | #ifndef ___bpf_narg
196 | #define ___bpf_narg(...) \
197 | ___bpf_nth(_, ##__VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
198 | #endif
199 |
200 | #define ___bpf_fill0(arr, p, x) \
201 | do \
202 | { \
203 | } while (0)
204 | #define ___bpf_fill1(arr, p, x) arr[p] = x
205 | #define ___bpf_fill2(arr, p, x, args...) \
206 | arr[p] = x; \
207 | ___bpf_fill1(arr, p + 1, args)
208 | #define ___bpf_fill3(arr, p, x, args...) \
209 | arr[p] = x; \
210 | ___bpf_fill2(arr, p + 1, args)
211 | #define ___bpf_fill4(arr, p, x, args...) \
212 | arr[p] = x; \
213 | ___bpf_fill3(arr, p + 1, args)
214 | #define ___bpf_fill5(arr, p, x, args...) \
215 | arr[p] = x; \
216 | ___bpf_fill4(arr, p + 1, args)
217 | #define ___bpf_fill6(arr, p, x, args...) \
218 | arr[p] = x; \
219 | ___bpf_fill5(arr, p + 1, args)
220 | #define ___bpf_fill7(arr, p, x, args...) \
221 | arr[p] = x; \
222 | ___bpf_fill6(arr, p + 1, args)
223 | #define ___bpf_fill8(arr, p, x, args...) \
224 | arr[p] = x; \
225 | ___bpf_fill7(arr, p + 1, args)
226 | #define ___bpf_fill9(arr, p, x, args...) \
227 | arr[p] = x; \
228 | ___bpf_fill8(arr, p + 1, args)
229 | #define ___bpf_fill10(arr, p, x, args...) \
230 | arr[p] = x; \
231 | ___bpf_fill9(arr, p + 1, args)
232 | #define ___bpf_fill11(arr, p, x, args...) \
233 | arr[p] = x; \
234 | ___bpf_fill10(arr, p + 1, args)
235 | #define ___bpf_fill12(arr, p, x, args...) \
236 | arr[p] = x; \
237 | ___bpf_fill11(arr, p + 1, args)
238 | #define ___bpf_fill(arr, args...) \
239 | ___bpf_apply(___bpf_fill, ___bpf_narg(args))(arr, 0, args)
240 |
241 | /*
242 | * BPF_SEQ_PRINTF to wrap bpf_seq_printf to-be-printed values
243 | * in a structure.
244 | */
245 | #define BPF_SEQ_PRINTF(seq, fmt, args...) \
246 | ( \
247 | { \
248 | static const char ___fmt[] = fmt; \
249 | unsigned long long ___param[___bpf_narg(args)]; \
250 | \
251 | _Pragma("GCC diagnostic push") \
252 | _Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
253 | ___bpf_fill(___param, args); \
254 | _Pragma("GCC diagnostic pop") \
255 | \
256 | bpf_seq_printf(seq, ___fmt, sizeof(___fmt), \
257 | ___param, sizeof(___param)); \
258 | })
259 |
260 | /*
261 | * BPF_SNPRINTF wraps the bpf_snprintf helper with variadic arguments instead of
262 | * an array of u64.
263 | */
264 | #define BPF_SNPRINTF(out, out_size, fmt, args...) \
265 | ( \
266 | { \
267 | static const char ___fmt[] = fmt; \
268 | unsigned long long ___param[___bpf_narg(args)]; \
269 | \
270 | _Pragma("GCC diagnostic push") \
271 | _Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
272 | ___bpf_fill(___param, args); \
273 | _Pragma("GCC diagnostic pop") \
274 | \
275 | bpf_snprintf(out, out_size, ___fmt, \
276 | ___param, sizeof(___param)); \
277 | })
278 |
279 | #ifdef BPF_NO_GLOBAL_DATA
280 | #define BPF_PRINTK_FMT_MOD
281 | #else
282 | #define BPF_PRINTK_FMT_MOD static const
283 | #endif
284 |
285 | #define __bpf_printk(fmt, ...) \
286 | ( \
287 | { \
288 | BPF_PRINTK_FMT_MOD char ____fmt[] = fmt; \
289 | bpf_trace_printk(____fmt, sizeof(____fmt), \
290 | ##__VA_ARGS__); \
291 | })
292 |
293 | /*
294 | * __bpf_vprintk wraps the bpf_trace_vprintk helper with variadic arguments
295 | * instead of an array of u64.
296 | */
297 | #define __bpf_vprintk(fmt, args...) \
298 | ( \
299 | { \
300 | static const char ___fmt[] = fmt; \
301 | unsigned long long ___param[___bpf_narg(args)]; \
302 | \
303 | _Pragma("GCC diagnostic push") \
304 | _Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
305 | ___bpf_fill(___param, args); \
306 | _Pragma("GCC diagnostic pop") \
307 | \
308 | bpf_trace_vprintk(___fmt, sizeof(___fmt), \
309 | ___param, sizeof(___param)); \
310 | })
311 |
312 | /* Use __bpf_printk when bpf_printk call has 3 or fewer fmt args
313 | * Otherwise use __bpf_vprintk
314 | */
315 | #define ___bpf_pick_printk(...) \
316 | ___bpf_nth(_, ##__VA_ARGS__, __bpf_vprintk, __bpf_vprintk, __bpf_vprintk, \
317 | __bpf_vprintk, __bpf_vprintk, __bpf_vprintk, __bpf_vprintk, \
318 | __bpf_vprintk, __bpf_vprintk, __bpf_printk /*3*/, __bpf_printk /*2*/, \
319 | __bpf_printk /*1*/, __bpf_printk /*0*/)
320 |
321 | /* Helper macro to print out debug messages */
322 | #define bpf_printk(fmt, args...) ___bpf_pick_printk(args)(fmt, ##args)
323 |
324 | #endif
--------------------------------------------------------------------------------
/examples/compile.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | clang -target bpf -Wall -O2 -g -c bpf-to-bpf.c -I/usr/include -o bpf-to-bpf
4 |
--------------------------------------------------------------------------------
/examples/datain:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cilium/coverbee/664438750fcead99e490bed7b47d8dd7ab1902d2/examples/datain
--------------------------------------------------------------------------------
/examples/demo.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Remove any existing pins
4 | sudo rm -Rf /sys/fs/bpf/covertest
5 | sudo mkdir -p /sys/fs/bpf/covertest
6 |
7 | # Compile the latests coverbee version
8 | go build -o coverbee ../cmd/coverbee/main.go
9 |
10 | # Load our program
11 | sudo ./coverbee load \
12 | --elf bpf-to-bpf \
13 | --covermap-pin /sys/fs/bpf/covertest/bpf-to-bpf-coverage \
14 | --prog-pin-dir /sys/fs/bpf/covertest \
15 | --block-list ./bpf-to-bpf-blocklist.json
16 |
17 | # Run the program 2 times with a test packet
18 | sudo bpftool prog run pinned /sys/fs/bpf/covertest/firewall_prog data_in ./datain repeat 2
19 |
20 | # Collect coverage information
21 | sudo ./coverbee cover \
22 | --covermap-pin /sys/fs/bpf/covertest/bpf-to-bpf-coverage \
23 | --block-list ./bpf-to-bpf-blocklist.json \
24 | --output bpf-to-bpf.html
25 |
26 | # Clean up after ourselfs
27 | sudo rm -Rf /sys/fs/bpf/covertest
28 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/cilium/coverbee
2 |
3 | go 1.21
4 |
5 | require (
6 | github.com/alecthomas/participle/v2 v2.0.0-beta.4
7 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883
8 | github.com/cilium/ebpf v0.15.1-0.20240722092859-a61222d2f07f
9 | github.com/davecgh/go-spew v1.1.1
10 | github.com/spf13/cobra v1.4.0
11 | golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2
12 | golang.org/x/tools v0.2.0
13 | )
14 |
15 | require (
16 | github.com/inconshreveable/mousetrap v1.0.0 // indirect
17 | github.com/sergi/go-diff v1.2.0 // indirect
18 | github.com/spf13/pflag v1.0.5 // indirect
19 | golang.org/x/sys v0.20.0 // indirect
20 | )
21 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/alecthomas/assert/v2 v2.0.3 h1:WKqJODfOiQG0nEJKFKzDIG3E29CN2/4zR9XGJzKIkbg=
2 | github.com/alecthomas/assert/v2 v2.0.3/go.mod h1:b/+1DI2Q6NckYi+3mXyH3wFb8qG37K/DuK80n7WefXA=
3 | github.com/alecthomas/participle/v2 v2.0.0-beta.4 h1:ublfGBm+x+p2j7KotHhrUMbKtejT7M0Gv1Mt1u3absw=
4 | github.com/alecthomas/participle/v2 v2.0.0-beta.4/go.mod h1:RC764t6n4L8D8ITAJv0qdokritYSNR3wV5cVwmIEaMM=
5 | github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
6 | github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
7 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
8 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
9 | github.com/cilium/ebpf v0.15.1-0.20240722092859-a61222d2f07f h1:FIeE/1Cu+LpKDPzINvRegeYHJ3WJRB44fiRm66wqSoE=
10 | github.com/cilium/ebpf v0.15.1-0.20240722092859-a61222d2f07f/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE=
11 | github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
13 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
15 | github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
16 | github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
17 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
18 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
19 | github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
20 | github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
21 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
22 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
23 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
24 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
25 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
26 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
27 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
28 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
29 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
30 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
31 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
32 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
33 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
34 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
35 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
36 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
37 | github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
38 | github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
39 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
40 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
41 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
42 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
43 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
44 | golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI=
45 | golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
46 | golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
47 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
48 | golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
49 | golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
50 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
51 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
52 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
53 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
54 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
55 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
56 |
--------------------------------------------------------------------------------
/instrumentation.go:
--------------------------------------------------------------------------------
1 | package coverbee
2 |
3 | import (
4 | "encoding/binary"
5 | "errors"
6 | "fmt"
7 | "io"
8 | "path/filepath"
9 | "unsafe"
10 |
11 | "github.com/cilium/coverbee/pkg/verifierlog"
12 | "github.com/cilium/ebpf"
13 | "github.com/cilium/ebpf/asm"
14 | "github.com/cilium/ebpf/btf"
15 | "github.com/davecgh/go-spew/spew"
16 | "golang.org/x/exp/slices"
17 | "golang.org/x/tools/cover"
18 | )
19 |
20 | // InstrumentAndLoadCollection will instrument the given collection spec and proceed to load it using the provided
21 | // options. Please refer to `InstrumentCollection` for more information about the instrumentation process.
22 | func InstrumentAndLoadCollection(
23 | coll *ebpf.CollectionSpec,
24 | opts ebpf.CollectionOptions,
25 | logWriter io.Writer,
26 | ) (*ebpf.Collection, []*BasicBlock, error) {
27 | blockList, err := InstrumentCollection(coll, logWriter)
28 | if err != nil {
29 | return nil, nil, fmt.Errorf("InstrumentCollection: %w", err)
30 | }
31 |
32 | if logWriter != nil {
33 | // Verbose
34 | opts.Programs.LogLevel = 2
35 | }
36 |
37 | loadedColl, err := ebpf.NewCollectionWithOptions(coll, opts)
38 |
39 | if logWriter != nil {
40 | fmt.Fprintln(logWriter, "=== Instrumented verifier logs ===")
41 | if loadedColl != nil {
42 | for name, prog := range loadedColl.Programs {
43 | fmt.Fprintln(logWriter, "---", name, "---")
44 | fmt.Fprintln(logWriter, prog.VerifierLog)
45 | }
46 | }
47 | if err != nil {
48 | var vErr *ebpf.VerifierError
49 | if errors.As(err, &vErr) {
50 | fmt.Fprintf(logWriter, "%+v\n", vErr)
51 | }
52 | }
53 | }
54 |
55 | return loadedColl, blockList, err
56 | }
57 |
58 | // InstrumentCollection adds instrumentation instructions to all programs contained within the given collection.
59 | // This "instrumentation" consists of an additional map with a single key and a value which is an array of 16-bit
60 | // counters. Each index of the array corresponds to the basic block index. The instrumentation code will increment
61 | // the counter just before the basic block is executed.
62 | //
63 | // The given spec is modified with this instrumentation. The whole process is logged to the `logWriter` and a list of
64 | // all the basic blocks are returned and can later be matched to the counters in the map.
65 | //
66 | // Steps of the function:
67 | // 1. Load the original programs and collect the verbose verifier log
68 | // 2. Parse the verifier log, which tells us which registers and stack slots are occupied at any given time.
69 | // 3. Convert the program into a CFG(Control Flow Graph)
70 | // 4. At the start of each program and bpf-to-bpf function, load the cover-map's index 0 and store the map value in a
71 | // available slot on the stack.
72 | // 5. At the start of each block, load an offset into the cover-map value, increment it, write it back. This requires 2
73 | // registers which can be clobbered. If only 1 or no registers are unused, store the register values to the stack
74 | // and restore values afterwards.
75 | // 6. Move symbols of the original code to the instrumented code so jumps and functions calls first pass by the
76 | // instrumentation.
77 | // 7. Load all modified program into the kernel.
78 | func InstrumentCollection(coll *ebpf.CollectionSpec, logWriter io.Writer) ([]*BasicBlock, error) {
79 | if logWriter != nil {
80 | fmt.Fprintln(logWriter, "=== Original program ===")
81 | for name, prog := range coll.Programs {
82 | fmt.Fprintln(logWriter, "---", name, "---")
83 | fmt.Fprintln(logWriter, prog.Instructions)
84 | }
85 | }
86 |
87 | // Clone the spec so we can load and unload without side effects
88 | clone := coll.Copy()
89 | clonedOpts := ebpf.CollectionOptions{
90 | Programs: ebpf.ProgramOptions{
91 | LogLevel: ebpf.LogLevelInstruction,
92 | },
93 | }
94 |
95 | cloneColl, err := ebpf.NewCollectionWithOptions(clone, clonedOpts)
96 | if err != nil {
97 | return nil, fmt.Errorf("load program: %w", err)
98 | }
99 |
100 | if logWriter != nil {
101 | fmt.Fprintln(logWriter, "=== Original verifier logs ===")
102 | for name, prog := range cloneColl.Programs {
103 | fmt.Fprintln(logWriter, "---", name, "---")
104 | fmt.Fprintln(logWriter, prog.VerifierLog)
105 | }
106 |
107 | fmt.Fprintln(logWriter, "\n=== Parsed verifier logs ===")
108 | for name, prog := range cloneColl.Programs {
109 | fmt.Fprintln(logWriter, "---", name, "---")
110 | for _, line := range verifierlog.ParseVerifierLog(prog.VerifierLog) {
111 | spew.Fdump(logWriter, line)
112 | }
113 | }
114 | }
115 |
116 | blockList := make([]*BasicBlock, 0)
117 |
118 | blockID := 0
119 | if logWriter != nil {
120 | fmt.Fprintln(logWriter, "\n=== Instrumentation ===")
121 | }
122 | for name, prog := range coll.Programs {
123 | mergedStates := verifierlog.MergedPerInstruction(cloneColl.Programs[name].VerifierLog)
124 | if logWriter != nil {
125 | fmt.Fprintln(logWriter, "---", name, "--- Merged states ---")
126 | for i, mergedState := range mergedStates {
127 | fmt.Fprintf(logWriter, "%5d: %s\n", i, mergedState.String())
128 | }
129 | }
130 |
131 | // TODO check per subprogram (it currently works, but uses way to much memory than is required)
132 | progMaxFPOff := 0
133 | for _, state := range mergedStates {
134 | for _, slot := range state.Stack {
135 | if slot.Offset > progMaxFPOff {
136 | progMaxFPOff = slot.Offset
137 | }
138 | }
139 | }
140 | coverMapPFOff := progMaxFPOff + 8
141 | regSave1FPOff := progMaxFPOff + 16
142 | regSave2FPOff := progMaxFPOff + 24
143 |
144 | if logWriter != nil {
145 | fmt.Fprintln(logWriter, "---", name, "--- Stack offset ---")
146 | fmt.Fprintln(logWriter, "Max used by prog:", progMaxFPOff)
147 | fmt.Fprintln(logWriter, "Cover map value:", coverMapPFOff)
148 | fmt.Fprintln(logWriter, "Reg save 1:", regSave1FPOff)
149 | fmt.Fprintln(logWriter, "Reg save 2:", regSave2FPOff)
150 | }
151 |
152 | blocks := ProgramBlocks(prog.Instructions)
153 | instn := 0
154 |
155 | if logWriter != nil {
156 | fmt.Fprintln(logWriter, "---", name, "--- Blocks ---")
157 | for i, block := range blocks {
158 | fmt.Fprint(logWriter, "Block ", i, ":\n")
159 | fmt.Fprintln(logWriter, block.Block)
160 | }
161 | }
162 |
163 | blockList = append(blockList, blocks...)
164 |
165 | newProgram := make([]asm.Instruction, 0, len(prog.Instructions)+2*len(blocks))
166 |
167 | subProgFuncs := make(map[string]bool)
168 | for _, inst := range prog.Instructions {
169 | if inst.IsFunctionCall() {
170 | subProgFuncs[inst.Reference()] = true
171 | }
172 | }
173 |
174 | for _, block := range blocks {
175 | instr := make(asm.Instructions, 0)
176 |
177 | blockSym := block.Block[0].Symbol()
178 | // At the start of each program/sub-program we need to lookup the the covermap value and store in in the
179 | // stack so we can access it while in the current stack frame.
180 | if subProgFuncs[blockSym] || name == blockSym {
181 | // 1. Get registers used by function
182 | progFunc := btf.FuncMetadata(&block.Block[0])
183 | if progFunc == nil {
184 | return nil, fmt.Errorf("can't find Func for '%s' in '%s': %w", blockSym, name, err)
185 | }
186 |
187 | funcProto, ok := progFunc.Type.(*btf.FuncProto)
188 | if !ok {
189 | return nil, fmt.Errorf("Func type for '%s' in '%s' is not a FuncProto", blockSym, name)
190 | }
191 |
192 | regCnt := len(funcProto.Params)
193 |
194 | // 2.1. Initialize all un-initialized registers
195 | // This allows us to assume we can always save a register to the stack
196 | instr = append(instr,
197 | asm.Mov.Imm(asm.R0, 0),
198 | )
199 | for i := asm.R1 + asm.Register(regCnt); i <= asm.R9; i++ {
200 | instr = append(instr,
201 | asm.Mov.Imm(i, 0),
202 | )
203 | }
204 |
205 | // 2.2. Store used registers in R6-R9 (and stack slot if all 5 regs are used)
206 | if regCnt == 5 {
207 | // We can store R1-R4 in R6-R9 but if a function uses all five registers we need to store
208 | // R5 on the stack.
209 | instr = append(instr,
210 | asm.StoreMem(asm.R10, -int16(regSave2FPOff), asm.R5, asm.DWord),
211 | )
212 | regCnt = 4
213 | }
214 |
215 | for i := asm.R1; i < asm.R1+asm.Register(regCnt); i++ {
216 | instr = append(instr,
217 | asm.Mov.Reg(i+5, i),
218 | )
219 | }
220 |
221 | instr = append(instr,
222 | // 3. Load map ptr
223 | asm.LoadMapPtr(asm.R1, 0).WithReference("coverbee_covermap"),
224 | // 4. Store key=0 in regSave1 slot
225 | asm.Mov.Reg(asm.R2, asm.R10),
226 | asm.Add.Imm(asm.R2, -int32(regSave1FPOff)),
227 | asm.StoreImm(asm.R2, 0, 0, asm.DWord),
228 | // 5. Lookup map value
229 | asm.FnMapLookupElem.Call(),
230 | // 6. Null check (exit on R0 = null)
231 | // Note: Exit with code 1, some program types have restrictions on return values.
232 | asm.Instruction{
233 | OpCode: asm.OpCode(asm.JumpClass).SetJumpOp(asm.JNE).SetSource(asm.ImmSource),
234 | Dst: asm.R0,
235 | Offset: 2,
236 | Constant: 0,
237 | },
238 | asm.Mov.Imm(asm.R0, 1),
239 | asm.Return(),
240 | // 7. Store map value on in coverMapFPOff
241 | asm.StoreMem(asm.R10, -int16(coverMapPFOff), asm.R0, asm.DWord),
242 | )
243 |
244 | // 8. Restore R1-R5
245 | for i := asm.R1; i < asm.R1+asm.Register(regCnt); i++ {
246 | instr = append(instr,
247 | asm.Mov.Reg(i, i+5),
248 | )
249 | }
250 |
251 | if len(funcProto.Params) == 5 {
252 | instr = append(instr,
253 | asm.LoadMem(asm.R5, asm.R10, -int16(regSave2FPOff), asm.DWord),
254 | )
255 | }
256 | }
257 |
258 | // Index which registers are sometimes used and which are never used
259 | var usedRegs [11]bool
260 |
261 | // It is possible that the number of merged states is lower than the instruction count if
262 | // the end of a program is dynamically dead code. (the verifier didn't reach it but it also doesn't error)
263 | if instn < len(mergedStates) && !mergedStates[instn].Unknown {
264 | // Index which registers are sometimes used and which are never used
265 | for _, reg := range mergedStates[instn].Registers {
266 | usedRegs[reg.Register] = true
267 | }
268 | } else {
269 | // Mark all registers as in use, that is the worst case assumption, but it should work even
270 | // without verifier log.
271 | for i := range usedRegs {
272 | usedRegs[i] = true
273 | }
274 | }
275 |
276 | var (
277 | unusedR1 asm.Register = 255
278 | unusedR2 asm.Register = 255
279 | )
280 |
281 | // Check each register, attempt to find two registers which are never used.
282 | for i := asm.R0; i <= asm.R9; i++ {
283 | if !usedRegs[i] {
284 | if unusedR1 == 255 {
285 | unusedR1 = i
286 | usedRegs[i] = true
287 | continue
288 | }
289 | if unusedR2 == 255 {
290 | unusedR2 = i
291 | break
292 | }
293 | }
294 | }
295 |
296 | // If we were unable to find an unused first register
297 | mapValR := unusedR1
298 | if mapValR == 255 {
299 | mapValR = asm.R8
300 | instr = append(instr,
301 | // Store R8 in stack for now
302 | asm.StoreMem(asm.R10, -int16(regSave1FPOff), mapValR, asm.DWord),
303 | )
304 | }
305 |
306 | // If we were unable to find an unused second register
307 | counterR := unusedR2
308 | if counterR == 255 {
309 | // In case we were able to use R9 as map val, we must pick R8 as counterR
310 | if mapValR == asm.R9 {
311 | counterR = asm.R8
312 | } else {
313 | counterR = asm.R9
314 | }
315 | instr = append(instr,
316 | // Store R9 in stack for now
317 | asm.StoreMem(asm.R10, -int16(regSave2FPOff), counterR, asm.DWord),
318 | )
319 | }
320 |
321 | instr = append(instr,
322 | // Load cover map value into `mapValR`
323 | asm.LoadMem(mapValR, asm.R10, -int16(coverMapPFOff), asm.DWord),
324 | // Get the current count of the blockID
325 | asm.LoadMem(counterR, mapValR, int16(blockID)*2, asm.Half),
326 | // Increment it
327 | asm.Add.Imm(counterR, 1),
328 | // Write it back
329 | asm.StoreMem(mapValR, int16(blockID)*2, counterR, asm.Half),
330 | )
331 |
332 | if unusedR1 == 255 {
333 | // Restore map value register if it was saved
334 | instr = append(instr,
335 | asm.LoadMem(mapValR, asm.R10, -int16(regSave1FPOff), asm.DWord),
336 | )
337 | }
338 |
339 | if unusedR2 == 255 {
340 | // Restore counter register if it was saved
341 | instr = append(instr,
342 | asm.LoadMem(counterR, asm.R10, -int16(regSave2FPOff), asm.DWord),
343 | )
344 | }
345 |
346 | // Move the metadata from head of the original code to the instrumented block so jumps and function calls
347 | // enter at the instrumented code first.
348 | newProgram = append(newProgram, instr[0].WithMetadata(block.Block[0].Metadata))
349 | newProgram = append(newProgram, instr[1:]...)
350 |
351 | // Remove the symbol and function metadata from the original start of the basic block since the symbol
352 | // was moved to the instrumented code for any jump targets along with the BTF function info.
353 | newProgram = append(newProgram, btf.WithFuncMetadata(block.Block[0].WithSymbol(""), nil))
354 | newProgram = append(newProgram, block.Block[1:]...)
355 |
356 | instn += int(block.Block.Size()) / asm.InstructionSize
357 |
358 | blockID++
359 | }
360 |
361 | if logWriter != nil {
362 | fmt.Fprintln(logWriter, "---", name, "--- Instrumented ---")
363 | fmt.Fprintln(logWriter, asm.Instructions(newProgram))
364 | }
365 |
366 | coll.Programs[name].Instructions = newProgram
367 | }
368 |
369 | cloneColl.Close()
370 |
371 | coverMap := ebpf.MapSpec{
372 | Name: "covermap",
373 | Type: ebpf.Array,
374 | KeySize: 4,
375 | MaxEntries: 1,
376 | ValueSize: uint32(2 * (blockID + 1)),
377 | }
378 | coll.Maps["coverbee_covermap"] = &coverMap
379 |
380 | return blockList, nil
381 | }
382 |
383 | // ProgramBlocks takes a list of instructions and converts it into a a CFG(Control Flow Graph).
384 | // Which works as follows:
385 | // 1. Construct a translation map from RawOffsets to the instructions(since index within the slice doesn't account for
386 | // LDIMM64 instructions which use two instructions).
387 | // 2. Apply a label to every jump target and set that label as a reference in the branching instruction. This does two
388 | // things. First, it makes it easy to find all block boundaries since each block has a function name or jump label.
389 | // The second is that cilium/ebpf will recalculate the offsets of the jumps based on the symbols when loading, so
390 | // we can easily add instructions to blocks without fear of breaking offsets.
391 | // 3. Loop over all instructions, creating a block at each branching instruction or symbol/jump label.
392 | // 4. Build a translation map from symbol/jump label to block.
393 | // 5. Loop over all blocks, using the map from step 4 to link blocks together on the branching and non-branching edges.
394 | func ProgramBlocks(prog asm.Instructions) []*BasicBlock {
395 | prog = slices.Clone(prog)
396 |
397 | // Make a RawInstOffset -> instruction lookup which improves performance during jump labeling
398 | iter := prog.Iterate()
399 | offToInst := map[asm.RawInstructionOffset]*asm.Instruction{}
400 | for iter.Next() {
401 | offToInst[iter.Offset] = iter.Ins
402 | }
403 |
404 | iter = prog.Iterate()
405 | for iter.Next() {
406 | inst := iter.Ins
407 |
408 | // Ignore non-jump ops, or "special" jump instructions
409 | op := inst.OpCode.JumpOp()
410 | switch op {
411 | case asm.InvalidJumpOp, asm.Call, asm.Exit:
412 | continue
413 | }
414 |
415 | targetOff := iter.Offset + asm.RawInstructionOffset(inst.Offset+1)
416 | label := fmt.Sprintf("j-%d", targetOff)
417 |
418 | target := offToInst[targetOff]
419 | *target = target.WithSymbol(label)
420 |
421 | inst.Offset = -1
422 | *inst = inst.WithReference(label)
423 | }
424 |
425 | blocks := make([]*BasicBlock, 0)
426 | curBlock := &BasicBlock{}
427 | for _, inst := range prog {
428 | if inst.Symbol() != "" {
429 | if len(curBlock.Block) > 0 {
430 | newBlock := &BasicBlock{
431 | Index: curBlock.Index + 1,
432 | }
433 | curBlock.NoBranch = newBlock
434 | blocks = append(blocks, curBlock)
435 | curBlock = newBlock
436 | }
437 | }
438 |
439 | curBlock.Block = append(curBlock.Block, inst)
440 |
441 | // Continue on non-jump ops
442 | op := inst.OpCode.JumpOp()
443 | if op == asm.InvalidJumpOp {
444 | continue
445 | }
446 |
447 | newBlock := &BasicBlock{
448 | Index: curBlock.Index + 1,
449 | }
450 |
451 | if op != asm.Exit {
452 | // If the current op is exit, then the current block will not continue into the block after it.
453 | curBlock.NoBranch = newBlock
454 | }
455 |
456 | blocks = append(blocks, curBlock)
457 | curBlock = newBlock
458 | }
459 |
460 | symToBlock := make(map[string]*BasicBlock)
461 | for _, block := range blocks {
462 | sym := block.Block[0].Symbol()
463 | if sym != "" {
464 | symToBlock[sym] = block
465 | }
466 | }
467 |
468 | for _, block := range blocks {
469 | lastInst := block.Block[len(block.Block)-1]
470 |
471 | // Ignore non-jump ops and exit's
472 | op := lastInst.OpCode.JumpOp()
473 | switch op {
474 | case asm.InvalidJumpOp, asm.Exit:
475 | continue
476 | }
477 |
478 | block.Branch = symToBlock[lastInst.Reference()]
479 | }
480 |
481 | return blocks
482 | }
483 |
484 | // BasicBlock is a block of non-branching code, which makes up a node within the CFG.
485 | type BasicBlock struct {
486 | Index int
487 | // The current block of code
488 | Block asm.Instructions
489 |
490 | // The next block of we don't branch
491 | NoBranch *BasicBlock
492 | // The next block if we do branch
493 | Branch *BasicBlock
494 | }
495 |
496 | // CFGToBlockList convert a CFG to a "BlockList", the outer slice indexed by BlockID which maps to an inner slice, each
497 | // element of which is a reference to a specific block of code inside a source file. Thus the resulting block list
498 | // can be used to translate blockID's into the pieces of source code to apply coverage mapping.
499 | func CFGToBlockList(cfg []*BasicBlock) [][]CoverBlock {
500 | blockList := make([][]CoverBlock, 0, len(cfg))
501 | for blockID, block := range cfg {
502 | blockList = append(blockList, make([]CoverBlock, 0))
503 | for _, inst := range block.Block {
504 | src := inst.Source()
505 | if src == nil {
506 | continue
507 | }
508 |
509 | line, ok := src.(*btf.Line)
510 | if !ok {
511 | continue
512 | }
513 |
514 | blockList[blockID] = append(blockList[blockID], CoverBlock{
515 | Filename: filepath.Clean(line.FileName()),
516 | ProfileBlock: cover.ProfileBlock{
517 | StartLine: int(line.LineNumber()),
518 | StartCol: 2,
519 | EndLine: int(line.LineNumber()),
520 | EndCol: 2000,
521 | NumStmt: 1,
522 | },
523 | })
524 | }
525 | }
526 |
527 | return blockList
528 | }
529 |
530 | // ApplyCoverMapToBlockList reads from the coverage map and applies the counts inside the map to the block list.
531 | // The blocklist can be iterated after this to create a go-cover coverage file.
532 | func ApplyCoverMapToBlockList(coverMap *ebpf.Map, blockList [][]CoverBlock) error {
533 | key := uint32(0)
534 | value := make([]byte, coverMap.ValueSize())
535 |
536 | err := coverMap.Lookup(&key, &value)
537 | if err != nil {
538 | return fmt.Errorf("error looking up coverage output: %w", err)
539 | }
540 |
541 | for blockID, lines := range blockList {
542 | blockCnt := nativeEndianess().Uint16(value[blockID*2 : (blockID+1)*2])
543 | for i := range lines {
544 | blockList[blockID][i].ProfileBlock.Count = int(blockCnt)
545 | }
546 | }
547 |
548 | return nil
549 | }
550 |
551 | var nativeEndian binary.ByteOrder
552 |
553 | func nativeEndianess() binary.ByteOrder {
554 | if nativeEndian != nil {
555 | return nativeEndian
556 | }
557 |
558 | buf := [2]byte{}
559 | *(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD)
560 |
561 | switch buf {
562 | case [2]byte{0xCD, 0xAB}:
563 | nativeEndian = binary.LittleEndian
564 | return nativeEndian
565 | case [2]byte{0xAB, 0xCD}:
566 | nativeEndian = binary.BigEndian
567 | return nativeEndian
568 | default:
569 | panic("Could not determine native endianness.")
570 | }
571 | }
572 |
--------------------------------------------------------------------------------
/parser.go:
--------------------------------------------------------------------------------
1 | package coverbee
2 |
3 | import (
4 | "fmt"
5 | "sort"
6 |
7 | "github.com/alecthomas/participle/v2/lexer"
8 | "github.com/cilium/coverbee/pkg/cparser"
9 | "golang.org/x/exp/slices"
10 | "golang.org/x/tools/cover"
11 | )
12 |
13 | // CTranslationUnitToBlocks turns a TranslationUnit into a list of cover blocks and a map which can be used to see
14 | // which AST nodes generated which blocks.
15 | func CTranslationUnitToBlocks(tu *cparser.TranslationUnit) ([]*CoverBlock, map[cparser.ASTNode][]*CoverBlock) {
16 | var blocks []*CoverBlock
17 |
18 | appendBlock := func(start, end lexer.Position, numStmt int) *CoverBlock {
19 | block := &CoverBlock{
20 | Filename: start.Filename,
21 | ProfileBlock: cover.ProfileBlock{
22 | StartLine: start.Line,
23 | StartCol: start.Column,
24 | EndLine: end.Line,
25 | EndCol: end.Column,
26 | NumStmt: numStmt,
27 | },
28 | }
29 | blocks = append(blocks, block)
30 | return block
31 | }
32 |
33 | nodeToBlockMap := make(map[cparser.ASTNode][]*CoverBlock)
34 |
35 | cparser.VisitDepthFirst(tu, func(node cparser.ASTNode, _ []cparser.ASTNode) {
36 | switch node := node.(type) {
37 | case *cparser.ExpressionStatement, *cparser.JumpStatement:
38 | block := appendBlock(node.GetHead(), node.GetTail(), 1)
39 | nodeToBlockMap[node] = append(nodeToBlockMap[node], block)
40 |
41 | case *cparser.SelectionStatement:
42 | block := appendBlock(node.GetHead(), node.ClosingBracket, 1)
43 | nodeToBlockMap[node] = append(nodeToBlockMap[node], block)
44 |
45 | if node.ElseToken != nil {
46 | elseEnd := *node.ElseToken
47 | elseEnd.Advance("else")
48 | block = appendBlock(*node.ElseToken, elseEnd, 1)
49 | nodeToBlockMap[node] = append(nodeToBlockMap[node], block)
50 | }
51 |
52 | case *cparser.ForStatement:
53 | block := appendBlock(node.GetHead(), node.ClosingBracket, 1)
54 | nodeToBlockMap[node] = append(nodeToBlockMap[node], block)
55 |
56 | case *cparser.WhileStatement:
57 | block := appendBlock(node.GetHead(), node.ClosingBracket, 1)
58 | nodeToBlockMap[node] = append(nodeToBlockMap[node], block)
59 | }
60 | })
61 |
62 | return blocks, nodeToBlockMap
63 | }
64 |
65 | // SourceCodeInterpolation parses all files referenced in the coverage block list and the additional file paths as C
66 | // code. It then uses the parsed code to construct coverage blocks from the source code instead of the compiled
67 | // object. This results in a more "complete" negative profile since it also will include lines which the compiler
68 | // tends to optimize out. It then applies the measured coverage to the source blocks. Lastly it will infer / interpolate
69 | // which lines of code must also have been evaluated given the AST and the coverage blocklist. The intended goal being
70 | // a more accurate report.
71 | func SourceCodeInterpolation(coverageBlockList [][]CoverBlock, additionalFilePaths []string) ([][]CoverBlock, error) {
72 | uniqueFiles := BlockListFilePaths(coverageBlockList)
73 |
74 | for _, additionalPath := range additionalFilePaths {
75 | i := sort.SearchStrings(uniqueFiles, additionalPath)
76 | if i < len(uniqueFiles) && uniqueFiles[i] == additionalPath {
77 | continue
78 | }
79 |
80 | // Insert sorted
81 | uniqueFiles = append(uniqueFiles, "")
82 | copy(uniqueFiles[i+1:], uniqueFiles[i:])
83 | uniqueFiles[i] = additionalPath
84 | }
85 |
86 | // Base blocks contains blocks for all possible paths of a file, not just the onces that made it into the final
87 | // binary.
88 | baseBlocks := make(map[string][]*CoverBlock)
89 | nodeToBlockMaps := make(map[string]map[cparser.ASTNode][]*CoverBlock)
90 | translationUnits := make(map[string]*cparser.TranslationUnit)
91 | for _, filepath := range uniqueFiles {
92 | tu, err := cparser.ParseFile(filepath)
93 | if err != nil {
94 | return nil, fmt.Errorf("cparser parse file: %w", err)
95 | }
96 | translationUnits[filepath] = tu
97 |
98 | baseBlocks[filepath], nodeToBlockMaps[filepath] = CTranslationUnitToBlocks(tu)
99 | }
100 |
101 | // Loop over all blocks that we got from the coverage. Apply the results to the baseBlocks.
102 | for _, coverBlocks := range coverageBlockList {
103 | for _, coverBlock := range coverBlocks {
104 | fileBlocks := baseBlocks[coverBlock.Filename]
105 |
106 | for i := range fileBlocks {
107 | fileBlock := fileBlocks[i]
108 |
109 | if blocksOverlap(fileBlock.ProfileBlock, coverBlock.ProfileBlock) {
110 | fileBlock.ProfileBlock.Count += coverBlock.ProfileBlock.Count
111 | }
112 | }
113 | }
114 | }
115 |
116 | // Coverage inference.
117 | // The compiler can optimize out some lines of code or not include them in the debug info. In this step we attempt
118 | // to infer which lines are implicitly covered as well. We know that adjacent expressions execute after each other
119 | // as long as no branching occurs.
120 | for filepath := range baseBlocks {
121 | tu := translationUnits[filepath]
122 | nodeToBlocks := nodeToBlockMaps[filepath]
123 |
124 | // Walk the AST
125 | cparser.VisitDepthFirst(tu, func(node cparser.ASTNode, parents []cparser.ASTNode) {
126 | blocks := nodeToBlocks[node]
127 | maxCnt := 0
128 | for _, block := range blocks {
129 | if maxCnt < block.ProfileBlock.Count {
130 | maxCnt = block.ProfileBlock.Count
131 | }
132 | }
133 | if maxCnt == 0 {
134 | return
135 | }
136 |
137 | // If one of the blocks associated with this node has been covered, walk up to the parents
138 | for i := len(parents) - 1; i >= 0; i-- {
139 | parent := parents[i]
140 | switch parent := parent.(type) {
141 | case *cparser.CompoundStatement:
142 | // example:
143 | // {
144 | // abc++; // not covered
145 | // char def = 1; // covered (`node`)
146 | // def++; // not covered
147 | // }
148 |
149 | if i+1 >= len(parents) {
150 | break
151 | }
152 |
153 | originBlock, ok := parents[i+1].(*cparser.BlockItem)
154 | if !ok {
155 | return
156 | }
157 | originIdx := slices.Index(parent.BlockItems, originBlock)
158 | if originIdx == -1 {
159 | break
160 | }
161 |
162 | // Walk up
163 | for ii := originIdx - 1; ii >= 0; ii-- {
164 | sibling := parent.BlockItems[ii]
165 | // If this sibling is a jump or labeled statement, we can't guarantee that the statement
166 | // above that was executed together with the one we are currently evaluating.
167 | if sibling.Statement.JumpStatement != nil || sibling.Statement.LabeledStatement != nil {
168 | break
169 | }
170 |
171 | if sibling.Statement.ExpressionStatement != nil {
172 | siblingBlocks := nodeToBlocks[sibling.Statement.ExpressionStatement]
173 | for _, siblingBlock := range siblingBlocks {
174 | if siblingBlock.ProfileBlock.Count < maxCnt {
175 | siblingBlock.ProfileBlock.Count = maxCnt
176 | }
177 | }
178 | }
179 | }
180 |
181 | // Walk down
182 | for ii := originIdx + 1; ii < len(parent.BlockItems); ii++ {
183 | sibling := parent.BlockItems[ii]
184 | // If this sibling is a jump, or selection statement, we can't guarantee that the
185 | // statement after it will have executed together with the node we are currently evaluating
186 | if sibling.Statement.JumpStatement != nil ||
187 | sibling.Statement.SelectionStatement != nil {
188 | break
189 | }
190 |
191 | if sibling.Statement.ExpressionStatement != nil {
192 | siblingBlocks := nodeToBlocks[sibling.Statement.ExpressionStatement]
193 | for _, siblingBlock := range siblingBlocks {
194 | if siblingBlock.ProfileBlock.Count < maxCnt {
195 | siblingBlock.ProfileBlock.Count = maxCnt
196 | }
197 | }
198 | }
199 | }
200 |
201 | case *cparser.SelectionStatement:
202 | if i+1 >= len(parents) {
203 | break
204 | }
205 |
206 | nodes := nodeToBlocks[parent]
207 |
208 | originStmt, ok := parents[i+1].(*cparser.Statement)
209 | if !ok {
210 | return
211 | }
212 | if parent.IfBody == originStmt || parent.SwitchBody == originStmt {
213 | if nodes[0].ProfileBlock.Count < maxCnt {
214 | nodes[0].ProfileBlock.Count = maxCnt
215 | }
216 | }
217 |
218 | if parent.ElseBody == originStmt {
219 | if nodes[1].ProfileBlock.Count < maxCnt {
220 | nodes[1].ProfileBlock.Count = maxCnt
221 | }
222 | }
223 | }
224 | }
225 | })
226 | }
227 |
228 | var outBlocks [][]CoverBlock
229 | for _, blocks := range baseBlocks {
230 | derefBlocks := make([]CoverBlock, len(blocks))
231 | for i := range blocks {
232 | derefBlocks[i] = *blocks[i]
233 | }
234 | outBlocks = append(outBlocks, derefBlocks)
235 | }
236 |
237 | return outBlocks, nil
238 | }
239 |
--------------------------------------------------------------------------------
/pkg/cparser/ast.go:
--------------------------------------------------------------------------------
1 | package cparser
2 |
3 | import (
4 | "github.com/alecthomas/participle/v2/lexer"
5 | )
6 |
7 | // ASTNode is implemented by all AST nodes in this package
8 | type ASTNode interface {
9 | Children() []ASTNode
10 | GetHead() lexer.Position
11 | GetTail() lexer.Position
12 | }
13 |
14 | // BaseNode contains basic shared features and is embedded by all nodes
15 | type BaseNode struct {
16 | // Head is the position of the starting character of a node
17 | Head lexer.Position
18 | // Tail is the position of the last character of a node
19 | Tail lexer.Position
20 | }
21 |
22 | // SetSpan sets both the head and tail position
23 | func (n *BaseNode) SetSpan(head, tail *lexer.Token) {
24 | n.SetHead(head)
25 | n.SetTail(tail)
26 | }
27 |
28 | // GetHead returns the position of the starting character of a node
29 | func (n *BaseNode) GetHead() lexer.Position {
30 | return n.Head
31 | }
32 |
33 | // BaseNode sets the positions of the first character, it takes the position from the given token
34 | func (n *BaseNode) SetHead(token *lexer.Token) {
35 | if token != nil {
36 | n.Head = token.Pos
37 | }
38 | }
39 |
40 | // GetTail returns the position of the last character of a node
41 | func (n *BaseNode) GetTail() lexer.Position {
42 | return n.Tail
43 | }
44 |
45 | // BaseNode sets the positions of the last character, it takes the position from the given token and adds its length
46 | func (n *BaseNode) SetTail(token *lexer.Token) {
47 | if token != nil {
48 | n.Tail = token.Pos
49 | n.Tail.Advance(token.Value)
50 | }
51 | }
52 |
53 | // TranslationUnit is a full file containing C code.
54 | // ISO/IEC 9899:TC2 - 6.9 External definitions
55 | type TranslationUnit struct {
56 | BaseNode
57 |
58 | ExternalDeclarations []*ExternalDeclaration
59 | }
60 |
61 | // Children returns the child nodes
62 | func (n *TranslationUnit) Children() []ASTNode {
63 | var children []ASTNode
64 | for _, v := range n.ExternalDeclarations {
65 | children = append(children, v)
66 | }
67 | return children
68 | }
69 |
70 | // ExternalDeclaration declares a type, global variable, or function
71 | // ISO/IEC 9899:TC2 - 6.9 External definitions
72 | type ExternalDeclaration struct {
73 | BaseNode
74 |
75 | FuncDef *FunctionDefinition
76 | Decl *Declaration
77 | }
78 |
79 | // Children returns the child nodes
80 | func (n *ExternalDeclaration) Children() []ASTNode {
81 | var children []ASTNode
82 | if n.FuncDef != nil {
83 | children = append(children, n.FuncDef)
84 | }
85 | if n.Decl != nil {
86 | children = append(children, n.Decl)
87 | }
88 | return children
89 | }
90 |
91 | // FunctionDefinition contains the function of a function
92 | // ISO/IEC 9899:TC2 - 6.9.1 Function definitions
93 | type FunctionDefinition struct {
94 | BaseNode
95 |
96 | // The keywords, return value, name and paramenters of the function.
97 | DeclaratorAndSpec TokenList
98 | // The function body
99 | CompoundStatement *CompoundStatement
100 | }
101 |
102 | // Children returns the child nodes
103 | func (n *FunctionDefinition) Children() []ASTNode {
104 | var children []ASTNode
105 | if n.CompoundStatement != nil {
106 | children = append(children, n.CompoundStatement)
107 | }
108 | return children
109 | }
110 |
111 | // CompoundStatement is a scope { } and all of the items within that scope
112 | // ISO/IEC 9899:TC2 - 6.8.2 Compound statement
113 | type CompoundStatement struct {
114 | BaseNode
115 |
116 | BlockItems []*BlockItem
117 | }
118 |
119 | // Children returns the child nodes
120 | func (n *CompoundStatement) Children() []ASTNode {
121 | var children []ASTNode
122 | for _, v := range n.BlockItems {
123 | children = append(children, v)
124 | }
125 | return children
126 | }
127 |
128 | // BlockItem is a variable declaration or "something else" aka a statement
129 | // ISO/IEC 9899:TC2 - 6.8.2 Compound statement
130 | type BlockItem struct {
131 | BaseNode
132 |
133 | Declaration *Declaration
134 | Statement *Statement
135 | }
136 |
137 | // Children returns the child nodes
138 | func (n *BlockItem) Children() []ASTNode {
139 | var children []ASTNode
140 | if n.Declaration != nil {
141 | children = append(children, n.Declaration)
142 | }
143 | if n.Statement != nil {
144 | children = append(children, n.Statement)
145 | }
146 | return children
147 | }
148 |
149 | // Declaration is a variable declaration
150 | // ISO/IEC 9899:TC2 - 6.7 Declarations
151 | type Declaration struct {
152 | BaseNode
153 | }
154 |
155 | // Children returns the child nodes
156 | func (n *Declaration) Children() []ASTNode {
157 | return nil
158 | }
159 |
160 | // Statement is code other than a variable declaration.
161 | // ISO/IEC 9899:TC2 - 6.8 Statements and blocks
162 | type Statement struct {
163 | BaseNode
164 |
165 | // A goto label, switch case or default case.
166 | LabeledStatement *LabeledStatement
167 | // A scope, { }
168 | CompoundStatement *CompoundStatement
169 | // Anything other than control flow, e.g ('abc++;' or 'def[abc] = ++somefunc();')
170 | ExpressionStatement *ExpressionStatement
171 | // A if or switch statement
172 | SelectionStatement *SelectionStatement
173 | // A loop (for, while, do-while)
174 | IterationStatement *IterationStatement
175 | // A jump (goto, continue, break)
176 | JumpStatement *JumpStatement
177 | }
178 |
179 | // Children returns the child nodes
180 | func (n *Statement) Children() []ASTNode {
181 | var children []ASTNode
182 | if n.LabeledStatement != nil {
183 | children = append(children, n.LabeledStatement)
184 | }
185 | if n.CompoundStatement != nil {
186 | children = append(children, n.CompoundStatement)
187 | }
188 | if n.ExpressionStatement != nil {
189 | children = append(children, n.ExpressionStatement)
190 | }
191 | if n.SelectionStatement != nil {
192 | children = append(children, n.SelectionStatement)
193 | }
194 | if n.IterationStatement != nil {
195 | children = append(children, n.IterationStatement)
196 | }
197 | if n.JumpStatement != nil {
198 | children = append(children, n.JumpStatement)
199 | }
200 | return children
201 | }
202 |
203 | // LabeledStatement is a goto label, switch case or switch default case.
204 | // ISO/IEC 9899:TC2 - 6.8.1 Labeled statements
205 | type LabeledStatement struct {
206 | BaseNode
207 |
208 | // Name of the label, ("case" or "default" in switch cases)
209 | Label string
210 | // The value of a switch case, nil otherwise
211 | ConstantExpression TokenList
212 | // The statement after the label
213 | Statement *Statement
214 | }
215 |
216 | // Children returns the child nodes
217 | func (n *LabeledStatement) Children() []ASTNode {
218 | var children []ASTNode
219 | if n.Statement != nil {
220 | children = append(children, n.Statement)
221 | }
222 | return children
223 | }
224 |
225 | // ExpressionStatement is anything other than control flow, e.g ('abc++;' or 'def[abc] = ++somefunc();')
226 | // ISO/IEC 9899:TC2 - 6.8.3 Expression and null statements
227 | type ExpressionStatement struct {
228 | BaseNode
229 |
230 | Tokens TokenList
231 | }
232 |
233 | // Children returns the child nodes
234 | func (n *ExpressionStatement) Children() []ASTNode {
235 | return nil
236 | }
237 |
238 | // SelectionStatement is a if, if-else, or switch case
239 | // ISO/IEC 9899:TC2 - 6.8.4 Selection statements
240 | type SelectionStatement struct {
241 | BaseNode
242 |
243 | // Additional position information which is particularly useful for code coverage annotation.
244 | ClosingBracket lexer.Position
245 | ElseToken *lexer.Position
246 |
247 | Expression TokenList
248 | IfBody *Statement
249 | ElseBody *Statement
250 | SwitchBody *Statement
251 | }
252 |
253 | // Children returns the child nodes
254 | func (n *SelectionStatement) Children() []ASTNode {
255 | var children []ASTNode
256 | if n.IfBody != nil {
257 | children = append(children, n.IfBody)
258 | }
259 | if n.ElseBody != nil {
260 | children = append(children, n.ElseBody)
261 | }
262 | if n.SwitchBody != nil {
263 | children = append(children, n.SwitchBody)
264 | }
265 | return children
266 | }
267 |
268 | // IterationStatement is a while, for, or do-while loop
269 | // ISO/IEC 9899:TC2 - 6.8.5 Iteration statements
270 | type IterationStatement struct {
271 | BaseNode
272 |
273 | While *WhileStatement
274 | For *ForStatement
275 | DoWhile *DoWhileStatement
276 | }
277 |
278 | // Children returns the child nodes
279 | func (n *IterationStatement) Children() []ASTNode {
280 | var children []ASTNode
281 | if n.While != nil {
282 | children = append(children, n.While)
283 | }
284 | if n.For != nil {
285 | children = append(children, n.For)
286 | }
287 | if n.DoWhile != nil {
288 | children = append(children, n.DoWhile)
289 | }
290 | return children
291 | }
292 |
293 | // WhileStatement is a while loop
294 | // ISO/IEC 9899:TC2 - 6.8.5 Iteration statements
295 | type WhileStatement struct {
296 | BaseNode
297 |
298 | // Closing bracket location, which is useful for coloring coverage reports.
299 | ClosingBracket lexer.Position
300 |
301 | GuardExpression TokenList
302 | Body *Statement
303 | }
304 |
305 | // Children returns the child nodes
306 | func (n *WhileStatement) Children() []ASTNode {
307 | var children []ASTNode
308 | if n.Body != nil {
309 | children = append(children, n.Body)
310 | }
311 | return children
312 | }
313 |
314 | // DoWhileStatement is a do-while loop
315 | // ISO/IEC 9899:TC2 - 6.8.5 Iteration statements
316 | type DoWhileStatement struct {
317 | BaseNode
318 |
319 | Body *Statement
320 | GuardExpression TokenList
321 | }
322 |
323 | // Children returns the child nodes
324 | func (n *DoWhileStatement) Children() []ASTNode {
325 | var children []ASTNode
326 | if n.Body != nil {
327 | children = append(children, n.Body)
328 | }
329 | return children
330 | }
331 |
332 | // ForStatement is a for loop
333 | // ISO/IEC 9899:TC2 - 6.8.5 Iteration statements
334 | type ForStatement struct {
335 | BaseNode
336 |
337 | ClosingBracket lexer.Position
338 |
339 | InitExpression TokenList
340 | GuardExpression TokenList
341 | IterationExpression TokenList
342 | Body *Statement
343 | }
344 |
345 | // Children returns the child nodes
346 | func (n *ForStatement) Children() []ASTNode {
347 | var children []ASTNode
348 | if n.Body != nil {
349 | children = append(children, n.Body)
350 | }
351 | return children
352 | }
353 |
354 | // JumpStatement is a continue, break, goto, or return statement.
355 | // ISO/IEC 9899:TC2 - 6.8.6 Jump statements
356 | type JumpStatement struct {
357 | BaseNode
358 |
359 | // "continue" or "break", empty if goto
360 | ContinueBreak string
361 | // The name of the goto label, empty if not goto
362 | GotoIdent string
363 | // The expression, the value of which is returned.
364 | ReturnExpression TokenList
365 | }
366 |
367 | // Children returns the child nodes
368 | func (n *JumpStatement) Children() []ASTNode {
369 | return nil
370 | }
371 |
372 | // VisitDepthFirst facilitates a depth first traversal of the AST. `fn` is called for every node in the sub-tree.
373 | // `parents` contains a slice of parents of the current node, len(parents)-1 being the direct parent and 0 being root.
374 | func VisitDepthFirst(node ASTNode, fn func(node ASTNode, parents []ASTNode)) {
375 | visitDepthFirst(node, fn, nil)
376 | }
377 |
378 | func visitDepthFirst(node ASTNode, fn func(node ASTNode, parents []ASTNode), parents []ASTNode) {
379 | fn(node, parents)
380 |
381 | newParents := append(parents, node)
382 |
383 | children := node.Children()
384 | for _, child := range children {
385 | visitDepthFirst(child, fn, newParents)
386 | }
387 | }
388 |
--------------------------------------------------------------------------------
/pkg/cparser/parser.go:
--------------------------------------------------------------------------------
1 | package cparser
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 |
8 | "github.com/alecthomas/participle/v2/lexer"
9 | )
10 |
11 | const (
12 | // TokenSingleLineComment = "SingleLineComment"
13 | tokenString = "String"
14 | tokenOctalNumber = "OctalNumber"
15 | tokenNumber = "Number"
16 | tokenHexNumber = "HexNumber"
17 | tokenBinaryNumber = "BinaryNumber"
18 | tokenIdent = "Ident"
19 | tokenPunct = "Punct"
20 | tokenEscapedEOL = "EscapedEOL"
21 | tokenEOL = "EOL"
22 | tokenWhitespace = "Whitespace"
23 | )
24 |
25 | var cLexer = lexer.MustSimple([]lexer.SimpleRule{
26 | // {Name: TokenSingleLineComment, Pattern: `//[^\n]*`},
27 | {Name: tokenString, Pattern: `"(\\"|[^"])*"`},
28 | {Name: tokenHexNumber, Pattern: `(0x|0X)[0-9a-fA-F]+`},
29 | {Name: tokenBinaryNumber, Pattern: `(0b)[01]+`},
30 | {Name: tokenOctalNumber, Pattern: `0[0-7]+`},
31 | {Name: tokenNumber, Pattern: `[-+]?(\d*\.)?\d+`},
32 | {Name: tokenIdent, Pattern: `[a-zA-Z_]\w*`},
33 | {Name: tokenPunct, Pattern: `[-[!@#$%^&*()+_={}\|:;"'<,>.?\\/~]|]`},
34 | {Name: tokenEscapedEOL, Pattern: `\\[\n\r]+`},
35 | {Name: tokenEOL, Pattern: `[\n\r]+`},
36 | {Name: tokenWhitespace, Pattern: `[ \t]+`},
37 | })
38 |
39 | // ParseFile attempts to parse a C file.
40 | func ParseFile(path string) (*TranslationUnit, error) {
41 | f, err := os.Open(path)
42 | if err != nil {
43 | return nil, fmt.Errorf("os open: %w", err)
44 | }
45 | defer f.Close()
46 |
47 | p, tl, err := NewParser(path, f)
48 | if err != nil {
49 | return nil, fmt.Errorf("new parser: %w", err)
50 | }
51 |
52 | return p.ParseTU(tl)
53 | }
54 |
55 | // TokenList is a list of tokens, the type contains a number of methods to make it easier to consume tokens.
56 | type TokenList []lexer.Token
57 |
58 | func newTokenList(list []lexer.Token) TokenList {
59 | return TokenList(list)
60 | }
61 |
62 | // Next returns the next token in the list and advances the internal cursor
63 | func (tl *TokenList) Next() *lexer.Token {
64 | if len(*tl) == 0 {
65 | return nil
66 | }
67 |
68 | token := &(*tl)[0]
69 | *tl = (*tl)[1:]
70 | return token
71 | }
72 |
73 | // Peek the next token without advancing the internal cursor
74 | func (tl *TokenList) Peek() *lexer.Token {
75 | return tl.PeekN(0)
76 | }
77 |
78 | // Peek a token `n` positions forward without advancing the internal cursor
79 | func (tl *TokenList) PeekN(n int) *lexer.Token {
80 | if len(*tl) <= n {
81 | return nil
82 | }
83 | if n < 0 {
84 | return nil
85 | }
86 |
87 | return &(*tl)[n]
88 | }
89 |
90 | // Peek the last token contained in the list
91 | func (tl *TokenList) PeekTail() *lexer.Token {
92 | return tl.PeekN(len(*tl) - 1)
93 | }
94 |
95 | // PeekSearch peeks ahead and searches all remaining tokens. If `fn` return true, we stop and return the matching index.
96 | func (tl *TokenList) PeekSearch(fn func(i int, t *lexer.Token) bool) int {
97 | for i := range *tl {
98 | if fn(i, &(*tl)[i]) {
99 | return i
100 | }
101 | }
102 |
103 | return -1
104 | }
105 |
106 | // PeekReverseSearch peeks backward and searches all remaining tokens.
107 | // If `fn` return true, we stop and return the matching index.
108 | func (tl *TokenList) PeekReverseSearch(fn func(i int, t *lexer.Token) bool) int {
109 | for i := len(*tl) - 1; i >= 0; i-- {
110 | if fn(i, &((*tl)[i])) {
111 | return i
112 | }
113 | }
114 |
115 | return -1
116 | }
117 |
118 | // Sub returns a new token list starting from the internal cursor
119 | func (tl *TokenList) Sub() TokenList {
120 | return newTokenList(*tl)
121 | }
122 |
123 | // SubN returns a new token list starting from the internal cursor and ending at `n`
124 | func (tl *TokenList) SubN(n int) TokenList {
125 | if len(*tl) == 0 {
126 | return newTokenList(*tl)
127 | }
128 | if n >= len(*tl) {
129 | n = len(*tl) - 1
130 | }
131 | if n < 0 {
132 | n = 0
133 | }
134 | return newTokenList((*tl)[:n+1])
135 | }
136 |
137 | func (tl *TokenList) Advance(n int) {
138 | if n >= len(*tl) {
139 | *tl = nil
140 | return
141 | }
142 |
143 | if n < 0 {
144 | return
145 | }
146 |
147 | *tl = (*tl)[n:]
148 | }
149 |
150 | // Parser is a C file parser
151 | type Parser struct {
152 | // All token in the current file
153 | syms map[string]lexer.TokenType
154 | }
155 |
156 | func newBadToken(token lexer.Token, expected string) error {
157 | return &UnexpectedTokenError{
158 | UnexpectedToken: token,
159 | Expected: expected,
160 | }
161 | }
162 |
163 | // UnexpectedTokenError also known as a syntax error
164 | type UnexpectedTokenError struct {
165 | UnexpectedToken lexer.Token
166 | Expected string
167 | }
168 |
169 | func (ute *UnexpectedTokenError) Error() string {
170 | return fmt.Sprintf(
171 | "Unexpected token '%s' at %s, expected %s",
172 | ute.UnexpectedToken.Value,
173 | ute.UnexpectedToken.Pos,
174 | ute.Expected,
175 | )
176 | }
177 |
178 | // NewParser creates a new parser for the given filename and contents.
179 | func NewParser(filename string, r io.Reader) (*Parser, TokenList, error) {
180 | lex, err := cLexer.Lex(filename, r)
181 | if err != nil {
182 | return nil, nil, fmt.Errorf("lex: %w", err)
183 | }
184 |
185 | syms := cLexer.Symbols()
186 | ignoredTokenTypes := []lexer.TokenType{
187 | syms["SingleLineComment"],
188 | syms["Whitespace"],
189 | }
190 |
191 | tokens := make(TokenList, 0)
192 | tokenloop:
193 | for {
194 | token, err := lex.Next()
195 | if err != nil {
196 | return nil, nil, fmt.Errorf("lex next: %w", err)
197 | }
198 |
199 | // Ignore some token types
200 | for _, it := range ignoredTokenTypes {
201 | if token.Type == it {
202 | continue tokenloop
203 | }
204 | }
205 |
206 | tokens = append(tokens, token)
207 |
208 | if token.EOF() {
209 | break
210 | }
211 | }
212 |
213 | parser := &Parser{
214 | syms: syms,
215 | }
216 |
217 | tokens = parser.filterCode(tokens)
218 |
219 | return &Parser{
220 | syms: syms,
221 | }, tokens, nil
222 | }
223 |
224 | func (p *Parser) filterCode(tokens TokenList) TokenList {
225 | var (
226 | result TokenList
227 | // Was the last token a new line?
228 | lastNewLine = true
229 |
230 | inSingleLineComment bool
231 | inMultiLineComment bool
232 | )
233 |
234 | for {
235 | cur := tokens.Next()
236 | if cur == nil {
237 | break
238 | }
239 |
240 | if inSingleLineComment {
241 | if cur.Type == p.EOL() {
242 | inSingleLineComment = false
243 | lastNewLine = true
244 | }
245 |
246 | // Don't include the comment token
247 | continue
248 | }
249 |
250 | if inMultiLineComment {
251 | // Look for '*/', the end of a multiline comment
252 | if cur.Value == "*" {
253 | next := tokens.Peek()
254 | if next != nil && next.Value == "/" {
255 | inMultiLineComment = false
256 | // Consume the peeked token if it was a '/'
257 | tokens.Next()
258 | }
259 | }
260 |
261 | // Don't include the comment token
262 | continue
263 | }
264 |
265 | if cur.Value == "/" {
266 | next := tokens.Peek()
267 | // handle the '//' comment case
268 | if next != nil && next.Value == "/" {
269 | inSingleLineComment = true
270 | tokens.Next()
271 | continue
272 | }
273 |
274 | // handle the '/*' comment case
275 | if next != nil && next.Value == "*" {
276 | inMultiLineComment = true
277 | tokens.Next()
278 | continue
279 | }
280 | }
281 |
282 | // If '#' at the start of a line, its a pre-processor statement(will treat it as a single line comment)
283 | if cur.Value == "#" && lastNewLine {
284 | inSingleLineComment = true
285 | continue
286 | }
287 |
288 | lastNewLine = cur.Type == p.EOL()
289 | if lastNewLine {
290 | continue
291 | }
292 |
293 | result = append(result, *cur)
294 | }
295 |
296 | return result
297 | }
298 |
299 | // ParseTU attempts to parse all tokens within the parser as a translation unit
300 | func (p *Parser) ParseTU(tokens TokenList) (*TranslationUnit, error) {
301 | // (6.9) translation-unit:
302 | // external-declaration
303 | // translation-unit external-declaration
304 | var tu TranslationUnit
305 |
306 | tu.Head = tokens[0].Pos
307 | tu.Tail = tokens[len(tokens)-1].Pos
308 |
309 | for {
310 | // Search for the end of a expression or the closing bracket of a function in the global scope
311 | scopeDepth := 0
312 | globalScopeIsFunc := false
313 | off := tokens.PeekSearch(func(i int, t *lexer.Token) bool {
314 | if t.Type != p.Punct() {
315 | return false
316 | }
317 |
318 | if scopeDepth == 0 {
319 | next := tokens.PeekN(i + 1)
320 | if t.Value == ")" && next != nil && next.Value == "{" {
321 | globalScopeIsFunc = true
322 | }
323 | }
324 |
325 | if t.Value == "{" {
326 | scopeDepth++
327 | }
328 |
329 | if t.Value == ";" && scopeDepth == 0 {
330 | return true
331 | }
332 |
333 | if t.Value == "}" {
334 | scopeDepth--
335 | if scopeDepth == 0 && globalScopeIsFunc {
336 | return true
337 | }
338 | }
339 |
340 | return false
341 | })
342 |
343 | var sub TokenList
344 | if off == -1 {
345 | sub = tokens.Sub()
346 | } else {
347 | sub = tokens.SubN(off)
348 | tokens.Advance(off + 1)
349 | }
350 |
351 | extDect, err := p.parseExternalDeclaration(sub)
352 | if err != nil {
353 | return nil, fmt.Errorf("parse external deceleration: %w", err)
354 | }
355 | if extDect != nil {
356 | tu.ExternalDeclarations = append(tu.ExternalDeclarations, extDect)
357 | }
358 |
359 | if off == -1 {
360 | break
361 | }
362 |
363 | if tokens.Peek() == nil || tokens.Peek().EOF() {
364 | break
365 | }
366 | }
367 |
368 | return &tu, nil
369 | }
370 |
371 | func (p *Parser) parseExternalDeclaration(tokens TokenList) (*ExternalDeclaration, error) {
372 | // (6.9) external-declaration:
373 | // function-definition
374 | // declaration
375 | last := tokens.PeekTail()
376 | if last == nil {
377 | return nil, fmt.Errorf("no last token")
378 | }
379 |
380 | // Discard trailing tokens.
381 | if last.EOF() {
382 | return nil, nil
383 | }
384 |
385 | var extDecl ExternalDeclaration
386 |
387 | switch last.Value {
388 | case ";":
389 | decl, err := p.parseDeclaration(tokens)
390 | if err != nil {
391 | return nil, fmt.Errorf("parse declaration: %w", err)
392 | }
393 |
394 | extDecl.Head = decl.Head
395 | extDecl.Tail = decl.Tail
396 | extDecl.Decl = decl
397 | case "}":
398 | funcDef, err := p.parseFunctionDefinition(tokens)
399 | if err != nil {
400 | return nil, fmt.Errorf("parse function definition: %w", err)
401 | }
402 |
403 | extDecl.Head = funcDef.Head
404 | extDecl.Tail = funcDef.Tail
405 | extDecl.FuncDef = funcDef
406 | default:
407 | return nil, newBadToken(*last, "';' or '}'")
408 | }
409 |
410 | return &extDecl, nil
411 | }
412 |
413 | func (p *Parser) parseDeclaration(tokens TokenList) (*Declaration, error) {
414 | // (6.7) declaration:
415 | // declaration-specifiers init-declarator-list[opt] ;
416 |
417 | // Don't continue parsing declarations, a more detailed break down isn't required for code coverage
418 | // since we are mostly interested in expressions.
419 |
420 | var decl Declaration
421 | decl.SetSpan(tokens.Next(), tokens.PeekTail())
422 | return &decl, nil
423 | }
424 |
425 | func (p *Parser) parseFunctionDefinition(tokens TokenList) (*FunctionDefinition, error) {
426 | // (6.9.1) function-definition:
427 | // declaration-specifiers declarator declaration-list[opt] compound-statement
428 |
429 | compoundStatementStart := tokens.PeekSearch(func(i int, t *lexer.Token) bool {
430 | return t.Value == "{"
431 | })
432 | if compoundStatementStart == -1 {
433 | return nil, fmt.Errorf("can't find start of compound statement")
434 | }
435 |
436 | var funcDef FunctionDefinition
437 | funcDef.SetHead(tokens.Peek())
438 |
439 | funcDef.DeclaratorAndSpec = tokens.SubN(compoundStatementStart - 1)
440 | tokens.Advance(compoundStatementStart)
441 |
442 | var err error
443 | funcDef.CompoundStatement, err = p.parseCompoundStatement(tokens)
444 | if err != nil {
445 | return nil, fmt.Errorf("parse compound statement: %w", err)
446 | }
447 | funcDef.Tail = funcDef.CompoundStatement.Tail
448 |
449 | return &funcDef, nil
450 | }
451 |
452 | func (p *Parser) parseCompoundStatement(tokens TokenList) (*CompoundStatement, error) {
453 | // (6.8.2) compound-statement:
454 | // { block-item-list[opt] }
455 |
456 | open := tokens.Next()
457 | if open == nil {
458 | return nil, fmt.Errorf("out of tokens")
459 | }
460 |
461 | if open.Value != "{" {
462 | return nil, newBadToken(*open, "'{'")
463 | }
464 |
465 | if tokens.PeekTail() == nil {
466 | return nil, fmt.Errorf("out of tokens")
467 | }
468 |
469 | tail := tokens.PeekTail()
470 | if tail.Value != "}" {
471 | return nil, newBadToken(*tail, "'}'")
472 | }
473 |
474 | tokens = tokens.SubN(len(tokens) - 2)
475 |
476 | var compStmt CompoundStatement
477 |
478 | compStmt.SetSpan(open, tail)
479 |
480 | for {
481 | var (
482 | block *BlockItem
483 | err error
484 | )
485 | block, tokens, err = p.parseBlockItem(tokens)
486 | if err != nil {
487 | return nil, fmt.Errorf("parse block item: %w", err)
488 | }
489 |
490 | compStmt.BlockItems = append(compStmt.BlockItems, block)
491 |
492 | if len(tokens) == 0 {
493 | break
494 | }
495 | }
496 |
497 | return &compStmt, nil
498 | }
499 |
500 | func (p *Parser) parseBlockItem(tokens TokenList) (*BlockItem, TokenList, error) {
501 | // (6.8.2) block-item:
502 | // declaration
503 | // statement
504 |
505 | // Statement or Declaration
506 | // TODO find out which, for now everything is an Statement
507 |
508 | var (
509 | block BlockItem
510 | err error
511 | )
512 |
513 | block.Statement, tokens, err = p.parseStatement(tokens)
514 | if err != nil {
515 | return nil, tokens, fmt.Errorf("parse statement: %w", err)
516 | }
517 |
518 | block.Head = block.Statement.Head
519 | block.Tail = block.Statement.Tail
520 |
521 | return &block, tokens, nil
522 | }
523 |
524 | func (p *Parser) parseStatement(tokens TokenList) (*Statement, TokenList, error) {
525 | first := tokens.Peek()
526 | next := tokens.PeekN(1)
527 | if first == nil {
528 | return nil, nil, fmt.Errorf("out of tokens")
529 | }
530 |
531 | var (
532 | stmt Statement
533 | err error
534 | )
535 |
536 | // (6.8.1) labeled-statement:
537 | // identifier : statement
538 | // case constant-expression : statement
539 | // default : statement
540 | if first.Type == p.Ident() && (next != nil && next.Value == ":") || first.Value == "case" {
541 | stmt.LabeledStatement, tokens, err = p.parseLabeledStatement(tokens)
542 | if err != nil {
543 | return nil, nil, fmt.Errorf("parse labeled statement: %w", err)
544 | }
545 | stmt.Head = stmt.LabeledStatement.Head
546 | stmt.Tail = stmt.LabeledStatement.Tail
547 | return &stmt, tokens, nil
548 | }
549 |
550 | // (6.8.2) compound-statement:
551 | // { block-item-list[opt] }
552 | if first.Value == "{" {
553 | depth := 0
554 | closingIdx := tokens.PeekSearch(func(i int, t *lexer.Token) bool {
555 | if t.Value == "{" {
556 | depth++
557 | }
558 | if t.Value == "}" {
559 | depth--
560 | if depth == 0 {
561 | return true
562 | }
563 | }
564 | return false
565 | })
566 | if closingIdx == -1 {
567 | return nil, nil, fmt.Errorf("can't find closing bracket for '%s' at %s", first.Value, first.Pos)
568 | }
569 |
570 | stmt.CompoundStatement, err = p.parseCompoundStatement(tokens.SubN(closingIdx))
571 | if err != nil {
572 | return nil, nil, fmt.Errorf("parse compound statement: %w", err)
573 | }
574 | tokens.Advance(closingIdx + 1)
575 | stmt.Head = stmt.CompoundStatement.Head
576 | stmt.Tail = stmt.CompoundStatement.Tail
577 | return &stmt, tokens, nil
578 | }
579 |
580 | // (6.8.4) selection-statement:
581 | // if ( expression ) statement
582 | // if ( expression ) statement else statement
583 | // switch ( expression ) statement
584 | if first.Value == "if" || first.Value == "switch" {
585 | stmt.SelectionStatement, tokens, err = p.parseSelectionStatement(tokens)
586 | if err != nil {
587 | return nil, nil, fmt.Errorf("parse selection statement: %w", err)
588 | }
589 | stmt.Head = stmt.SelectionStatement.Head
590 | stmt.Tail = stmt.SelectionStatement.Tail
591 | return &stmt, tokens, nil
592 | }
593 |
594 | // (6.8.5) iteration-statement:
595 | // while ( expression ) statement
596 | // do statement while ( expression ) ;
597 | // for ( expressionopt ; expressionopt ; expressionopt ) statement
598 | // for ( declaration expressionopt ; expressionopt ) statement
599 | if first.Value == "while" || first.Value == "do" || first.Value == "for" {
600 | stmt.IterationStatement, tokens, err = p.parseIterationStatement(tokens)
601 | if err != nil {
602 | return nil, nil, fmt.Errorf("parse iteration statement: %w", err)
603 | }
604 | stmt.Head = stmt.IterationStatement.Head
605 | stmt.Tail = stmt.IterationStatement.Tail
606 | return &stmt, tokens, nil
607 | }
608 |
609 | // (6.8.6) jump-statement:
610 | // goto identifier ;
611 | // continue ;
612 | // break ;
613 | // return expressionopt ;
614 | if first.Value == "goto" || first.Value == "continue" || first.Value == "break" || first.Value == "return" {
615 | stmt.JumpStatement, tokens, err = p.parseJumpStatement(tokens)
616 | if err != nil {
617 | return nil, nil, fmt.Errorf("parse iteration statement: %w", err)
618 | }
619 | stmt.Head = stmt.JumpStatement.Head
620 | stmt.Tail = stmt.JumpStatement.Tail
621 | return &stmt, tokens, nil
622 | }
623 |
624 | // expression-statement:
625 | // expression[opt] ;
626 |
627 | depth := 0
628 | semicolonIdx := tokens.PeekSearch(func(i int, t *lexer.Token) bool {
629 | if t.Value == "{" {
630 | depth++
631 | }
632 | if t.Value == "}" {
633 | depth--
634 | }
635 |
636 | return depth == 0 && t.Value == ";"
637 | })
638 |
639 | stmt.ExpressionStatement = &ExpressionStatement{}
640 | if semicolonIdx == -1 {
641 | stmt.ExpressionStatement.Tokens = tokens.Sub()
642 | tokens = nil
643 | } else {
644 | stmt.ExpressionStatement.Tokens = tokens.SubN(semicolonIdx)
645 | tokens.Advance(semicolonIdx + 1)
646 | }
647 | head := stmt.ExpressionStatement.Tokens.Peek()
648 | tail := stmt.ExpressionStatement.Tokens.PeekTail()
649 | stmt.ExpressionStatement.SetSpan(head, tail)
650 | stmt.SetSpan(head, tail)
651 | return &stmt, tokens, nil
652 | }
653 |
654 | func (p *Parser) parseLabeledStatement(tokens TokenList) (*LabeledStatement, TokenList, error) {
655 | // (6.8.1) labeled-statement:
656 | // identifier : statement
657 | // case constant-expression : statement
658 | // default : statement
659 |
660 | first := tokens.Next()
661 | if first == nil {
662 | return nil, nil, fmt.Errorf("out of tokens")
663 | }
664 |
665 | var labelStmt LabeledStatement
666 |
667 | labelStmt.SetHead(first)
668 |
669 | if first.Value == "case" {
670 | colonIdx := tokens.PeekSearch(func(i int, t *lexer.Token) bool {
671 | return t.Value == ":"
672 | })
673 | if colonIdx == -1 {
674 | return nil, nil, fmt.Errorf("can't find case colon")
675 | }
676 |
677 | labelStmt.Label = "case"
678 | labelStmt.ConstantExpression = tokens.SubN(colonIdx - 1)
679 | tokens.Advance(colonIdx + 1)
680 | } else {
681 | second := tokens.Next()
682 | if second == nil {
683 | return nil, nil, fmt.Errorf("out of tokens")
684 | }
685 |
686 | if first.Type != p.Ident() {
687 | return nil, nil, newBadToken(*first, "")
688 | }
689 |
690 | if second.Value != ":" {
691 | return nil, nil, newBadToken(*first, "':'")
692 | }
693 |
694 | labelStmt.Label = first.Value
695 | }
696 |
697 | subStmt, tail, err := p.parseStatement(tokens)
698 | if err != nil {
699 | return nil, nil, fmt.Errorf("parse statement: %w", err)
700 | }
701 |
702 | labelStmt.Statement = subStmt
703 | labelStmt.Tail = subStmt.Tail
704 |
705 | return &labelStmt, tail, nil
706 | }
707 |
708 | func (p *Parser) parseSelectionStatement(tokens TokenList) (*SelectionStatement, TokenList, error) {
709 | // (6.8.4) selection-statement:
710 | // if ( expression ) statement
711 | // if ( expression ) statement else statement
712 | // switch ( expression ) statement
713 | first := tokens.Next()
714 | second := tokens.Peek()
715 |
716 | if first == nil || second == nil {
717 | return nil, nil, fmt.Errorf("out of tokens")
718 | }
719 |
720 | if first.Value != "if" && first.Value != "switch" {
721 | return nil, tokens, newBadToken(*first, "'if' or 'switch'")
722 | }
723 |
724 | if second.Value != "(" {
725 | return nil, tokens, newBadToken(*second, "'('")
726 | }
727 |
728 | // Search for the closing brace matching the opening one
729 | depth := 0
730 | closingIdx := tokens.PeekSearch(func(i int, t *lexer.Token) bool {
731 | if t.Value == "(" {
732 | depth++
733 | }
734 | if t.Value == ")" {
735 | depth--
736 | if depth == 0 {
737 | return true
738 | }
739 | }
740 | return false
741 | })
742 | if closingIdx == -1 {
743 | return nil, tokens, fmt.Errorf("unable to find closing bracket for expression")
744 | }
745 |
746 | var selStmt SelectionStatement
747 |
748 | selStmt.SetHead(first)
749 | selStmt.ClosingBracket = tokens.PeekN(closingIdx + 1).Pos
750 | selStmt.Expression = tokens.SubN(closingIdx)
751 | tokens.Advance(closingIdx + 1)
752 |
753 | subStmt, tail, err := p.parseStatement(tokens)
754 | if err != nil {
755 | return nil, tokens, fmt.Errorf("parse statement: %w", err)
756 | }
757 | selStmt.Tail = subStmt.Tail
758 |
759 | if first.Value == "switch" {
760 | selStmt.SwitchBody = subStmt
761 | return &selStmt, tail, nil
762 | }
763 |
764 | selStmt.IfBody = subStmt
765 |
766 | if tail.Peek() == nil || tail.Peek().Value != "else" {
767 | return &selStmt, tail, nil
768 | }
769 | selStmt.ElseToken = &tail.Peek().Pos
770 | tail.Advance(1)
771 |
772 | subStmt, tail, err = p.parseStatement(tail)
773 | if err != nil {
774 | return nil, tokens, fmt.Errorf("parse statement: %w", err)
775 | }
776 |
777 | selStmt.ElseBody = subStmt
778 | selStmt.Tail = subStmt.Tail
779 |
780 | return &selStmt, tail, nil
781 | }
782 |
783 | func (p *Parser) parseIterationStatement(tokens TokenList) (*IterationStatement, TokenList, error) {
784 | // (6.8.5) iteration-statement:
785 | // while ( expression ) statement
786 | // do statement while ( expression ) ;
787 | // for ( expressionopt ; expressionopt ; expressionopt ) statement
788 | // for ( declaration expressionopt ; expressionopt ) statement
789 |
790 | first := tokens.Peek()
791 | if first == nil {
792 | return nil, nil, fmt.Errorf("out of tokens")
793 | }
794 |
795 | var iterStmt IterationStatement
796 |
797 | switch first.Value {
798 | case "while":
799 | whileExpr, tail, err := p.parseWhileExpression(tokens)
800 | if err != nil {
801 | return nil, nil, fmt.Errorf("parse while expr: %w", err)
802 | }
803 | iterStmt.While = whileExpr
804 | iterStmt.Head = whileExpr.Head
805 | iterStmt.Tail = whileExpr.Tail
806 | return &iterStmt, tail, nil
807 |
808 | case "for":
809 | forExpr, tail, err := p.parseForExpression(tokens)
810 | if err != nil {
811 | return nil, nil, fmt.Errorf("parse while expr: %w", err)
812 | }
813 | iterStmt.For = forExpr
814 | iterStmt.Head = forExpr.Head
815 | iterStmt.Tail = forExpr.Tail
816 | return &iterStmt, tail, nil
817 |
818 | case "do":
819 | doWhileExpr, tail, err := p.parseDoWhileExpression(tokens)
820 | if err != nil {
821 | return nil, nil, fmt.Errorf("parse while expr: %w", err)
822 | }
823 | iterStmt.DoWhile = doWhileExpr
824 | iterStmt.Head = doWhileExpr.Head
825 | iterStmt.Tail = doWhileExpr.Tail
826 | return &iterStmt, tail, nil
827 | }
828 |
829 | return nil, nil, newBadToken(*first, "'while', 'for', or 'do'")
830 | }
831 |
832 | func (p *Parser) parseWhileExpression(tokens TokenList) (*WhileStatement, TokenList, error) {
833 | // while ( expression ) statement
834 | first := tokens.Next()
835 | second := tokens.Peek()
836 | if first == nil || second == nil {
837 | return nil, nil, fmt.Errorf("out of tokens")
838 | }
839 |
840 | if first.Value != "while" {
841 | return nil, nil, newBadToken(*first, "'while'")
842 | }
843 |
844 | if second.Value != "(" {
845 | return nil, nil, newBadToken(*first, "'('")
846 | }
847 |
848 | var whileExpr WhileStatement
849 | whileExpr.SetHead(first)
850 |
851 | // Search for the closing brace matching the opening one
852 | depth := 0
853 | closingIdx := tokens.PeekSearch(func(i int, t *lexer.Token) bool {
854 | if t.Value == "(" {
855 | depth++
856 | }
857 | if t.Value == ")" {
858 | depth--
859 | if depth == 0 {
860 | return true
861 | }
862 | }
863 | return false
864 | })
865 | if closingIdx == -1 {
866 | return nil, tokens, fmt.Errorf("unable to find closing bracket for expression")
867 | }
868 |
869 | whileExpr.GuardExpression = tokens.SubN(closingIdx)
870 | whileExpr.ClosingBracket = tokens.PeekN(closingIdx + 1).Pos
871 | tokens.Advance(closingIdx + 1)
872 |
873 | bodyStmt, tail, err := p.parseStatement(tokens)
874 | if err != nil {
875 | return nil, nil, fmt.Errorf("parse statement: %w", err)
876 | }
877 | whileExpr.Body = bodyStmt
878 | whileExpr.Tail = bodyStmt.Tail
879 |
880 | return &whileExpr, tail, nil
881 | }
882 |
883 | func (p *Parser) parseForExpression(tokens TokenList) (*ForStatement, TokenList, error) {
884 | // for ( expressionopt ; expressionopt ; expressionopt ) statement
885 | // for ( declaration expressionopt ; expressionopt ) statement
886 |
887 | first := tokens.Next()
888 | second := tokens.Peek()
889 | if first == nil || second == nil {
890 | return nil, nil, fmt.Errorf("out of tokens")
891 | }
892 |
893 | if first.Value != "for" {
894 | return nil, nil, newBadToken(*first, "'for'")
895 | }
896 |
897 | if second.Value != "(" {
898 | return nil, nil, newBadToken(*first, "'('")
899 | }
900 |
901 | var forStmt ForStatement
902 | forStmt.SetHead(first)
903 |
904 | // Search for the closing brace matching the opening one
905 | depth := 0
906 | closingIdx := tokens.PeekSearch(func(i int, t *lexer.Token) bool {
907 | if t.Value == "(" {
908 | depth++
909 | }
910 | if t.Value == ")" {
911 | depth--
912 | if depth == 0 {
913 | return true
914 | }
915 | }
916 | return false
917 | })
918 | if closingIdx == -1 {
919 | return nil, tokens, fmt.Errorf("unable to find closing bracket for expression")
920 | }
921 |
922 | forStmt.ClosingBracket = tokens.PeekN(closingIdx + 1).Pos
923 |
924 | tokens.Advance(1)
925 | header := tokens.SubN(closingIdx - 2)
926 | tokens.Advance(closingIdx + 1)
927 |
928 | semiIdx := header.PeekSearch(func(i int, t *lexer.Token) bool {
929 | return t.Value == ";"
930 | })
931 | if semiIdx == -1 {
932 | return nil, nil, fmt.Errorf("missing first ';'")
933 | }
934 |
935 | forStmt.InitExpression = header.SubN(semiIdx - 1)
936 | header.Advance(semiIdx + 1)
937 |
938 | semiIdx = header.PeekSearch(func(i int, t *lexer.Token) bool {
939 | return t.Value == ";"
940 | })
941 | if semiIdx == -1 {
942 | forStmt.GuardExpression = header.Sub()
943 | } else {
944 | forStmt.GuardExpression = header.SubN(semiIdx - 1)
945 | header.Advance(semiIdx + 1)
946 | forStmt.IterationExpression = header.Sub()
947 | }
948 |
949 | body, tail, err := p.parseStatement(tokens)
950 | if err != nil {
951 | return nil, nil, fmt.Errorf("parse statement: %w", err)
952 | }
953 | forStmt.Body = body
954 | forStmt.Tail = body.Tail
955 |
956 | return &forStmt, tail, nil
957 | }
958 |
959 | func (p *Parser) parseDoWhileExpression(tokens TokenList) (*DoWhileStatement, TokenList, error) {
960 | // do statement while ( expression ) ;
961 |
962 | first := tokens.Next()
963 | if first == nil {
964 | return nil, nil, fmt.Errorf("out of tokens")
965 | }
966 |
967 | if first.Value != "do" {
968 | return nil, nil, newBadToken(*first, "'do'")
969 | }
970 |
971 | var doWhileStmt DoWhileStatement
972 |
973 | doWhileStmt.SetHead(first)
974 |
975 | body, tokens, err := p.parseStatement(tokens)
976 | if err != nil {
977 | return nil, nil, fmt.Errorf("parse statement: %w", err)
978 | }
979 | doWhileStmt.Body = body
980 |
981 | whileToken := tokens.Next()
982 | if whileToken == nil {
983 | return nil, nil, fmt.Errorf("out of tokens")
984 | }
985 |
986 | if whileToken.Value != "while" {
987 | return nil, nil, newBadToken(*whileToken, "'while'")
988 | }
989 |
990 | open := tokens.Peek()
991 | if open == nil {
992 | return nil, nil, fmt.Errorf("out of tokens")
993 | }
994 |
995 | if open.Value != "(" {
996 | return nil, nil, newBadToken(*whileToken, "'('")
997 | }
998 |
999 | // Search for the closing brace matching the opening one
1000 | depth := 0
1001 | closingIdx := tokens.PeekSearch(func(i int, t *lexer.Token) bool {
1002 | if t.Value == "(" {
1003 | depth++
1004 | }
1005 | if t.Value == ")" {
1006 | depth--
1007 | if depth == 0 {
1008 | return true
1009 | }
1010 | }
1011 | return false
1012 | })
1013 | if closingIdx == -1 {
1014 | return nil, tokens, fmt.Errorf("unable to find closing bracket for expression")
1015 | }
1016 |
1017 | tokens.Advance(1)
1018 | doWhileStmt.GuardExpression = tokens.SubN(closingIdx - 2)
1019 | tokens.Advance(closingIdx)
1020 |
1021 | last := tokens.Next()
1022 | if last == nil {
1023 | return nil, nil, fmt.Errorf("out of tokens")
1024 | }
1025 |
1026 | if last.Value != ";" {
1027 | return nil, nil, newBadToken(*last, "';'")
1028 | }
1029 | doWhileStmt.SetTail(last)
1030 |
1031 | return &doWhileStmt, tokens, nil
1032 | }
1033 |
1034 | func (p *Parser) parseJumpStatement(tokens TokenList) (*JumpStatement, TokenList, error) {
1035 | // (6.8.6) jump-statement:
1036 | // goto identifier ;
1037 | // continue ;
1038 | // break ;
1039 | // return expressionopt ;
1040 |
1041 | first := tokens.Next()
1042 | if first == nil {
1043 | return nil, nil, fmt.Errorf("out of tokens")
1044 | }
1045 |
1046 | var jmpStmt JumpStatement
1047 |
1048 | jmpStmt.SetHead(first)
1049 |
1050 | switch first.Value {
1051 | case "goto":
1052 | second := tokens.Next()
1053 | if second == nil {
1054 | return nil, nil, fmt.Errorf("out of tokens")
1055 | }
1056 |
1057 | jmpStmt.GotoIdent = second.Value
1058 |
1059 | case "continue", "break":
1060 | jmpStmt.ContinueBreak = first.Value
1061 |
1062 | case "return":
1063 | semiIdx := tokens.PeekSearch(func(i int, t *lexer.Token) bool {
1064 | return t.Value == ";"
1065 | })
1066 | if semiIdx == -1 {
1067 | return nil, nil, fmt.Errorf("can't find semicolon")
1068 | }
1069 |
1070 | jmpStmt.ReturnExpression = tokens.SubN(semiIdx - 1)
1071 | tokens.Advance(semiIdx)
1072 | default:
1073 | return nil, nil, newBadToken(*first, "'goto', 'continue', 'break', or 'return'")
1074 | }
1075 |
1076 | last := tokens.Next()
1077 | if last == nil {
1078 | return nil, nil, fmt.Errorf("out of tokens")
1079 | }
1080 |
1081 | if last.Value != ";" {
1082 | return nil, nil, newBadToken(*last, "';'")
1083 | }
1084 | jmpStmt.SetTail(last)
1085 |
1086 | return &jmpStmt, tokens, nil
1087 | }
1088 |
1089 | // Ident returns the lexer token type for a identifier
1090 | func (p *Parser) Ident() lexer.TokenType {
1091 | return p.syms[tokenIdent]
1092 | }
1093 |
1094 | // Punct returns the lexer token type for a punctuation
1095 | func (p *Parser) Punct() lexer.TokenType {
1096 | return p.syms[tokenPunct]
1097 | }
1098 |
1099 | // EOL returns the lexer token type for a end of line token
1100 | func (p *Parser) EOL() lexer.TokenType {
1101 | return p.syms[tokenEOL]
1102 | }
1103 |
--------------------------------------------------------------------------------
/pkg/verifierlog/.gitignore:
--------------------------------------------------------------------------------
1 | testdata/
2 |
--------------------------------------------------------------------------------
/pkg/verifierlog/verifierlog_test.go:
--------------------------------------------------------------------------------
1 | package verifierlog
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 |
7 | "github.com/andreyvit/diff"
8 | "github.com/cilium/ebpf/asm"
9 | "github.com/davecgh/go-spew/spew"
10 | )
11 |
12 | var seedLog = []string{
13 | `func#4 @114
14 | 0: R1=ctx(id=0,off=0,imm=0) R10=fp0`,
15 | `; __be16 h_proto = eth->h_proto;
16 | 6: (71) r3 = *(u8 *)(r1 +12) ; R1_w=pkt(id=0,off=0,r=14,imm=0) R3_w=invP(id=0,umax_value=255,var_off=(0x0; 0xff))`,
17 | `22: (85) call pc+4
18 | reg type unsupported for arg#0 function handle_ipv4#23
19 | caller:
20 | R6=invP2 R10=fp0
21 | callee:
22 | frame1: R1=pkt(id=0,off=0,r=18,imm=0) R2=pkt_end(id=0,off=0,imm=0) R3=invP18 R4=invP8 R10=fp0`,
23 | `; static __noinline void handle_ipv4(void *data, void *data_end, __u64 nh_off)
24 | 27: (bf) r8 = r3 ; frame1: R3=invP18 R8_w=invP18
25 |
26 | `,
27 | `from 94 to 107: frame2: R0=map_value(id=0,off=0,ks=2,vs=16,imm=0) R6=invP(id=5) R10=fp0 fp-8=mm??????
28 | ; stats_ptr->pkts++;`,
29 | `from 57 to 23: R0=invP(id=0) R6=invP2 R10=fp0
30 | ; handle_ipv4(data, data_end, nh_off);
31 | 23: (05) goto pc+1
32 | propagating r6
33 | 25: safe`,
34 | `26: (95) exit
35 | processed 520 insns (limit 1000000) max_states_per_insn 1 total_states 46 peak_states 46 mark_read 7`,
36 | }
37 |
38 | func FuzzParseVerifierLog(f *testing.F) {
39 | for _, log := range seedLog {
40 | f.Add(log)
41 | }
42 |
43 | f.Fuzz(func(t *testing.T, log string) {
44 | ParseVerifierLog(log)
45 | })
46 | }
47 |
48 | func Test_parseStatement(t *testing.T) {
49 | tests := []struct {
50 | name string
51 | log string
52 | want []VerifierStatement
53 | }{
54 | {
55 | name: "Backtrack instruction",
56 | log: "regs=100 stack=0 before 1: (b7) r8 = 0",
57 | want: []VerifierStatement{
58 | &BackTrackInstruction{
59 | Regs: []byte{0x1, 0x00},
60 | Stack: 0,
61 | Instruction: &Instruction{
62 | InstructionNumber: 1,
63 | Opcode: asm.OpCode(0xb7),
64 | Assembly: " r8 = 0",
65 | },
66 | },
67 | },
68 | },
69 | {
70 | name: "Instruction state #1",
71 | log: "36: (69) r1 = *(u16 *)(r7 +46) ; R1_w=inv(id=0,umax_value=65535,var_off=(0x0; 0xffff)) R7_w=map_value(id=0,off=0,ks=4,vs=100,imm=0)",
72 | want: []VerifierStatement{
73 | &InstructionState{
74 | Instruction: Instruction{
75 | InstructionNumber: 36,
76 | Opcode: asm.OpCode(0x69),
77 | Assembly: " r1 = *(u16 *)(r7 +46) ",
78 | },
79 | State: VerifierState{
80 | Registers: []RegisterState{
81 | {
82 | Register: asm.R1,
83 | Liveness: LivenessWritten,
84 | Value: RegisterValue{
85 | Type: RegTypeScalarValue,
86 | UMaxValue: 65535,
87 | VarOff: TNum{
88 | Mask: 0x0,
89 | Value: 0xffff,
90 | },
91 | },
92 | },
93 | {
94 | Register: asm.R7,
95 | Liveness: LivenessWritten,
96 | Value: RegisterValue{
97 | Type: RegTypeMapValue,
98 | KeySize: 4,
99 | ValueSize: 100,
100 | },
101 | },
102 | },
103 | },
104 | },
105 | },
106 | },
107 | {
108 | name: "Backtracking trailer",
109 | log: "parent didn't have regs=8 stack=0 marks: R0=inv(id=0) R1_w=inv(id=0,umax_value=65519,var_off=(0x0; 0xffef)) R2_w=inv(id=0) R3_rw=invP16 R6=map_value(id=0,off=56,ks=4,vs=8192,imm=0) R7_w=inv60 R8=pkt(id=0,off=0,r=34,imm=0) R9=map_value(id=0,off=0,ks=14,vs=56,imm=0) R10=fp0 fp-8=???????m fp-16=????mmmm fp-24=mmmmmmmm fp-32=mmmmmmmm fp-40=mmmmmmmm fp-48=mmmmmmmm fp-56=mmmmmmmm fp-64=mmmmmmmm fp-72=mmmmmmmm fp-80=mmmmmmmm fp-88=mmmmmmmm fp-96=??mmmmmm fp-104=mmmm0000 fp-112=map_value fp-120=ctx fp-128=map_ptr fp-136=inv fp-144=pkt_end fp-152=00000000 fp-160=00000000 fp-168=inv fp-176=00000000",
110 | want: []VerifierStatement{
111 | &BackTrackingTrailer{
112 | ParentMatch: false,
113 | Regs: []byte{0x08},
114 | Stack: 0,
115 | VerifierState: &VerifierState{
116 | Registers: []RegisterState{
117 | // R0=inv(id=0)
118 | {
119 | Register: asm.R0,
120 | Value: RegisterValue{
121 | Type: RegTypeScalarValue,
122 | },
123 | },
124 | // R1_w=inv(id=0,umax_value=65519,var_off=(0x0; 0xffef))
125 | {
126 | Register: asm.R1,
127 | Liveness: LivenessWritten,
128 | Value: RegisterValue{
129 | Type: RegTypeScalarValue,
130 | UMaxValue: 65519,
131 | VarOff: TNum{
132 | Mask: 0x0,
133 | Value: 0xffef,
134 | },
135 | },
136 | },
137 | // R2_w=inv(id=0)
138 | {
139 | Register: asm.R2,
140 | Liveness: LivenessWritten,
141 | Value: RegisterValue{
142 | Type: RegTypeScalarValue,
143 | },
144 | },
145 | // R3_rw=invP16
146 | {
147 | Register: asm.R3,
148 | Liveness: LivenessRead | LivenessWritten,
149 | Value: RegisterValue{
150 | Type: RegTypeScalarValue,
151 | Precise: true,
152 | VarOff: TNum{
153 | Mask: 0,
154 | Value: 16,
155 | },
156 | },
157 | },
158 | // R6=map_value(id=0,off=56,ks=4,vs=8192,imm=0)
159 | {
160 | Register: asm.R6,
161 | Value: RegisterValue{
162 | Type: RegTypeMapValue,
163 | Off: 56,
164 | KeySize: 4,
165 | ValueSize: 8192,
166 | },
167 | },
168 | // R7_w=inv60
169 | {
170 | Register: asm.R7,
171 | Liveness: LivenessWritten,
172 | Value: RegisterValue{
173 | Type: RegTypeScalarValue,
174 | VarOff: TNum{
175 | Mask: 0,
176 | Value: 60,
177 | },
178 | },
179 | },
180 | // R8=pkt(id=0,off=0,r=34,imm=0)
181 | {
182 | Register: asm.R8,
183 | Value: RegisterValue{
184 | Type: RegTypePtrToPacket,
185 | Range: 34,
186 | },
187 | },
188 | // R9=map_value(id=0,off=0,ks=14,vs=56,imm=0)
189 | {
190 | Register: asm.R9,
191 | Value: RegisterValue{
192 | Type: RegTypeMapValue,
193 | KeySize: 14,
194 | ValueSize: 56,
195 | },
196 | },
197 | // R10=fp0
198 | {
199 | Register: asm.R10,
200 | Value: RegisterValue{
201 | Type: RegTypePtrToStack,
202 | },
203 | },
204 | },
205 | Stack: []StackState{
206 | // fp-8=???????m
207 | {
208 | Offset: 8,
209 | Slots: [8]StackSlot{
210 | StackSlotInvalid,
211 | StackSlotInvalid,
212 | StackSlotInvalid,
213 | StackSlotInvalid,
214 | StackSlotInvalid,
215 | StackSlotInvalid,
216 | StackSlotInvalid,
217 | StackSlotMisc,
218 | },
219 | },
220 | // fp-16=????mmmm
221 | {
222 | Offset: 16,
223 | Slots: [8]StackSlot{
224 | StackSlotInvalid,
225 | StackSlotInvalid,
226 | StackSlotInvalid,
227 | StackSlotInvalid,
228 | StackSlotMisc,
229 | StackSlotMisc,
230 | StackSlotMisc,
231 | StackSlotMisc,
232 | },
233 | },
234 | // fp-24=mmmmmmmm
235 | {
236 | Offset: 24,
237 | Slots: [8]StackSlot{
238 | StackSlotMisc,
239 | StackSlotMisc,
240 | StackSlotMisc,
241 | StackSlotMisc,
242 | StackSlotMisc,
243 | StackSlotMisc,
244 | StackSlotMisc,
245 | StackSlotMisc,
246 | },
247 | },
248 | // fp-32=mmmmmmmm
249 | {
250 | Offset: 32,
251 | Slots: [8]StackSlot{
252 | StackSlotMisc,
253 | StackSlotMisc,
254 | StackSlotMisc,
255 | StackSlotMisc,
256 | StackSlotMisc,
257 | StackSlotMisc,
258 | StackSlotMisc,
259 | StackSlotMisc,
260 | },
261 | },
262 | // fp-40=mmmmmmmm
263 | {
264 | Offset: 40,
265 | Slots: [8]StackSlot{
266 | StackSlotMisc,
267 | StackSlotMisc,
268 | StackSlotMisc,
269 | StackSlotMisc,
270 | StackSlotMisc,
271 | StackSlotMisc,
272 | StackSlotMisc,
273 | StackSlotMisc,
274 | },
275 | },
276 | // fp-48=mmmmmmmm
277 | {
278 | Offset: 48,
279 | Slots: [8]StackSlot{
280 | StackSlotMisc,
281 | StackSlotMisc,
282 | StackSlotMisc,
283 | StackSlotMisc,
284 | StackSlotMisc,
285 | StackSlotMisc,
286 | StackSlotMisc,
287 | StackSlotMisc,
288 | },
289 | },
290 | // fp-56=mmmmmmmm
291 | {
292 | Offset: 56,
293 | Slots: [8]StackSlot{
294 | StackSlotMisc,
295 | StackSlotMisc,
296 | StackSlotMisc,
297 | StackSlotMisc,
298 | StackSlotMisc,
299 | StackSlotMisc,
300 | StackSlotMisc,
301 | StackSlotMisc,
302 | },
303 | },
304 | // fp-64=mmmmmmmm
305 | {
306 | Offset: 64,
307 | Slots: [8]StackSlot{
308 | StackSlotMisc,
309 | StackSlotMisc,
310 | StackSlotMisc,
311 | StackSlotMisc,
312 | StackSlotMisc,
313 | StackSlotMisc,
314 | StackSlotMisc,
315 | StackSlotMisc,
316 | },
317 | },
318 | // fp-72=mmmmmmmm
319 | {
320 | Offset: 72,
321 | Slots: [8]StackSlot{
322 | StackSlotMisc,
323 | StackSlotMisc,
324 | StackSlotMisc,
325 | StackSlotMisc,
326 | StackSlotMisc,
327 | StackSlotMisc,
328 | StackSlotMisc,
329 | StackSlotMisc,
330 | },
331 | },
332 | // fp-80=mmmmmmmm
333 | {
334 | Offset: 80,
335 | Slots: [8]StackSlot{
336 | StackSlotMisc,
337 | StackSlotMisc,
338 | StackSlotMisc,
339 | StackSlotMisc,
340 | StackSlotMisc,
341 | StackSlotMisc,
342 | StackSlotMisc,
343 | StackSlotMisc,
344 | },
345 | },
346 | // fp-88=mmmmmmmm
347 | {
348 | Offset: 88,
349 | Slots: [8]StackSlot{
350 | StackSlotMisc,
351 | StackSlotMisc,
352 | StackSlotMisc,
353 | StackSlotMisc,
354 | StackSlotMisc,
355 | StackSlotMisc,
356 | StackSlotMisc,
357 | StackSlotMisc,
358 | },
359 | },
360 | // fp-96=??mmmmmm
361 | {
362 | Offset: 96,
363 | Slots: [8]StackSlot{
364 | StackSlotInvalid,
365 | StackSlotInvalid,
366 | StackSlotMisc,
367 | StackSlotMisc,
368 | StackSlotMisc,
369 | StackSlotMisc,
370 | StackSlotMisc,
371 | StackSlotMisc,
372 | },
373 | },
374 | // fp-104=mmmm0000
375 | {
376 | Offset: 104,
377 | Slots: [8]StackSlot{
378 | StackSlotMisc,
379 | StackSlotMisc,
380 | StackSlotMisc,
381 | StackSlotMisc,
382 | StackSlotZero,
383 | StackSlotZero,
384 | StackSlotZero,
385 | StackSlotZero,
386 | },
387 | },
388 | // fp-112=map_value
389 | {
390 | Offset: 112,
391 | SpilledRegister: RegisterValue{
392 | Type: RegTypeMapValue,
393 | },
394 | },
395 | // fp-120=ctx
396 | {
397 | Offset: 120,
398 | SpilledRegister: RegisterValue{
399 | Type: RegTypePtrToCtx,
400 | },
401 | },
402 | // fp-128=map_ptr
403 | {
404 | Offset: 128,
405 | SpilledRegister: RegisterValue{
406 | Type: RegTypeConstPtrToMap,
407 | },
408 | },
409 | // fp-136=inv
410 | {
411 | Offset: 136,
412 | SpilledRegister: RegisterValue{
413 | Type: RegTypeScalarValue,
414 | },
415 | },
416 | // fp-144=pkt_end
417 | {
418 | Offset: 144,
419 | SpilledRegister: RegisterValue{
420 | Type: RegTypePtrToPacketEnd,
421 | },
422 | },
423 | // fp-152=00000000
424 | {
425 | Offset: 152,
426 | Slots: [8]StackSlot{
427 | StackSlotZero,
428 | StackSlotZero,
429 | StackSlotZero,
430 | StackSlotZero,
431 | StackSlotZero,
432 | StackSlotZero,
433 | StackSlotZero,
434 | StackSlotZero,
435 | },
436 | },
437 | // fp-160=00000000
438 | {
439 | Offset: 160,
440 | Slots: [8]StackSlot{
441 | StackSlotZero,
442 | StackSlotZero,
443 | StackSlotZero,
444 | StackSlotZero,
445 | StackSlotZero,
446 | StackSlotZero,
447 | StackSlotZero,
448 | StackSlotZero,
449 | },
450 | },
451 | // fp-168=inv
452 | {
453 | Offset: 168,
454 | SpilledRegister: RegisterValue{
455 | Type: RegTypeScalarValue,
456 | },
457 | },
458 | // fp-176=00000000
459 | {
460 | Offset: 176,
461 | Slots: [8]StackSlot{
462 | StackSlotZero,
463 | StackSlotZero,
464 | StackSlotZero,
465 | StackSlotZero,
466 | StackSlotZero,
467 | StackSlotZero,
468 | StackSlotZero,
469 | StackSlotZero,
470 | },
471 | },
472 | },
473 | },
474 | },
475 | },
476 | },
477 | }
478 | for _, tt := range tests {
479 | t.Run(tt.name, func(t *testing.T) {
480 | if got := ParseVerifierLog(tt.log); !reflect.DeepEqual(got, tt.want) {
481 | spew.Config.DisableMethods = true
482 | t.Errorf(
483 | "parseStatement() diff %s",
484 | diff.LineDiff(spew.Sdump(got), spew.Sdump(tt.want)),
485 | )
486 | spew.Config.DisableMethods = false
487 | }
488 | })
489 | }
490 | }
491 |
--------------------------------------------------------------------------------
/testdata/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tools/check-go-mod.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | go mod tidy
4 | STATUS=$( git status --porcelain go.mod go.sum )
5 | if [ ! -z "$STATUS" ]; then
6 | echo "Running go mod tidy modified go.mod and/or go.sum"
7 | exit 1
8 | fi
9 | exit 0
10 |
--------------------------------------------------------------------------------