├── .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 | [![Go Reference](https://pkg.go.dev/badge/github.com/cilium/coverbee.svg)](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 | 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 | 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 | 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 | 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 | --------------------------------------------------------------------------------