github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/gadgets/traceloop/tracer/syscall_helpers.go (about) 1 // Copyright 2019-2023 The Inspektor Gadget authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 //go:build !withoutebpf 16 17 package tracer 18 19 import ( 20 "fmt" 21 "io" 22 "os" 23 "path/filepath" 24 "regexp" 25 "strings" 26 27 "github.com/inspektor-gadget/inspektor-gadget/pkg/utils/syscalls" 28 ) 29 30 const syscallsPath = `/sys/kernel/debug/tracing/events/syscalls/` 31 32 type param struct { 33 position int 34 name string 35 isPointer bool 36 } 37 38 type syscallDeclaration struct { 39 name string 40 params []param 41 } 42 43 func syscallGetName(nr uint16) string { 44 name, ok := syscalls.GetSyscallNameByNumber(int(nr)) 45 // Just do like strace (https://man7.org/linux/man-pages/man1/strace.1.html): 46 // Syscalls unknown to strace are printed raw 47 if !ok { 48 return fmt.Sprintf("syscall_%x", nr) 49 } 50 51 return name 52 } 53 54 // TODO Find all syscalls which take a char * as argument and add them there. 55 var syscallDefs = map[string][6]uint64{ 56 "execve": {useNullByteLength, 0, 0, 0, 0, 0}, 57 "access": {useNullByteLength, 0, 0, 0, 0, 0}, 58 "open": {useNullByteLength, 0, 0, 0, 0, 0}, 59 "openat": {0, useNullByteLength, 0, 0, 0, 0}, 60 "mkdir": {useNullByteLength, 0, 0, 0, 0, 0}, 61 "chdir": {useNullByteLength, 0, 0, 0, 0, 0}, 62 "pivot_root": {useNullByteLength, useNullByteLength, 0, 0, 0, 0}, 63 "mount": {useNullByteLength, useNullByteLength, useNullByteLength, 0, 0, 0}, 64 "umount2": {useNullByteLength, 0, 0, 0, 0, 0}, 65 "sethostname": {useNullByteLength, 0, 0, 0, 0, 0}, 66 "statfs": {useNullByteLength, 0, 0, 0, 0, 0}, 67 "stat": {useNullByteLength, 0, 0, 0, 0, 0}, 68 "statx": {0, useNullByteLength, 0, 0, 0, 0}, 69 "lstat": {useNullByteLength, 0, 0, 0, 0, 0}, 70 "fgetxattr": {0, useNullByteLength, 0, 0, 0, 0}, 71 "lgetxattr": {useNullByteLength, useNullByteLength, 0, 0, 0, 0}, 72 "getxattr": {useNullByteLength, useNullByteLength, 0, 0, 0, 0}, 73 "newfstatat": {0, useNullByteLength, 0, 0, 0, 0}, 74 "read": {0, useRetAsParamLength | paramProbeAtExitMask, 0, 0, 0, 0}, 75 "write": {0, useArgIndexAsParamLength + 2, 0, 0, 0, 0}, 76 "getcwd": {useNullByteLength | paramProbeAtExitMask, 0, 0, 0, 0, 0}, 77 "pread64": {0, useRetAsParamLength | paramProbeAtExitMask, 0, 0, 0, 0}, 78 } 79 80 var re = regexp.MustCompile(`\s+field:(?P<type>.*?) (?P<name>[a-z_0-9]+);.*`) 81 82 func parseLine(l string, idx int) (*param, error) { 83 n1 := re.SubexpNames() 84 85 r := re.FindAllStringSubmatch(l, -1) 86 if len(r) == 0 { 87 return nil, nil 88 } 89 res := r[0] 90 91 mp := map[string]string{} 92 for i, n := range res { 93 mp[n1[i]] = n 94 } 95 96 if _, ok := mp["type"]; !ok { 97 return nil, nil 98 } 99 if _, ok := mp["name"]; !ok { 100 return nil, nil 101 } 102 103 // ignore 104 if mp["name"] == "__syscall_nr" { 105 return nil, nil 106 } 107 108 var cParam param 109 cParam.name = mp["name"] 110 111 // The position is calculated based on the event format. The actual parameters 112 // start from 8th index, hence we subtract that from idx to get position 113 // of the parameter to the syscall 114 cParam.position = idx - 8 115 116 cParam.isPointer = strings.Contains(mp["type"], "*") 117 118 return &cParam, nil 119 } 120 121 // Map sys_enter_NAME to syscall name as in /usr/include/asm/unistd_64.h 122 // TODO Check if this is also true for arm64. 123 func relateSyscallName(name string) string { 124 switch name { 125 case "newfstat": 126 return "fstat" 127 case "newlstat": 128 return "lstat" 129 case "newstat": 130 return "stat" 131 case "newuname": 132 return "uname" 133 case "sendfile64": 134 return "sendfile" 135 case "sysctl": 136 return "_sysctl" 137 case "umount": 138 return "umount2" 139 default: 140 return name 141 } 142 } 143 144 func parseSyscall(name, format string) (*syscallDeclaration, error) { 145 syscallParts := strings.Split(format, "\n") 146 var skipped bool 147 148 var cParams []param 149 for idx, line := range syscallParts { 150 if !skipped { 151 if len(line) != 0 { 152 continue 153 } else { 154 skipped = true 155 } 156 } 157 cp, err := parseLine(line, idx) 158 if err != nil { 159 return nil, err 160 } 161 if cp != nil { 162 cParams = append(cParams, *cp) 163 } 164 } 165 166 return &syscallDeclaration{ 167 name: name, 168 params: cParams, 169 }, nil 170 } 171 172 func gatherSyscallsDeclarations() (map[string]syscallDeclaration, error) { 173 cSyscalls := make(map[string]syscallDeclaration) 174 err := filepath.Walk(syscallsPath, func(path string, f os.FileInfo, err error) error { 175 if err != nil { 176 return err 177 } 178 179 if path == "syscalls" { 180 return nil 181 } 182 183 if !f.IsDir() { 184 return nil 185 } 186 187 eventName := f.Name() 188 if strings.HasPrefix(eventName, "sys_exit") { 189 return nil 190 } 191 192 syscallName := strings.TrimPrefix(eventName, "sys_enter_") 193 syscallName = relateSyscallName(syscallName) 194 195 formatFilePath := filepath.Join(syscallsPath, eventName, "format") 196 formatFile, err := os.Open(formatFilePath) 197 if err != nil { 198 return nil 199 } 200 defer formatFile.Close() 201 202 formatBytes, err := io.ReadAll(formatFile) 203 if err != nil { 204 return err 205 } 206 207 cSyscall, err := parseSyscall(syscallName, string(formatBytes)) 208 if err != nil { 209 return err 210 } 211 212 cSyscalls[cSyscall.name] = *cSyscall 213 214 return nil 215 }) 216 if err != nil { 217 return nil, fmt.Errorf("walking %q: %w", syscallsPath, err) 218 } 219 return cSyscalls, nil 220 } 221 222 func getSyscallDeclaration(syscallsDeclarations map[string]syscallDeclaration, syscallName string) (syscallDeclaration, error) { 223 declaration, ok := syscallsDeclarations[syscallName] 224 if !ok { 225 return syscallDeclaration{}, fmt.Errorf("no syscall correspond to %q", syscallName) 226 } 227 228 return declaration, nil 229 } 230 231 func (s syscallDeclaration) getParameterCount() uint8 { 232 return uint8(len(s.params)) 233 } 234 235 func (s syscallDeclaration) paramIsPointer(paramNumber uint8) (bool, error) { 236 if int(paramNumber) >= len(s.params) { 237 return false, fmt.Errorf("param number %d out of bounds for syscall %q", paramNumber, s.name) 238 } 239 return s.params[paramNumber].isPointer, nil 240 } 241 242 func (s syscallDeclaration) getParameterName(paramNumber uint8) (string, error) { 243 if int(paramNumber) >= len(s.params) { 244 return "", fmt.Errorf("param number %d out of bounds for syscall %q", paramNumber, s.name) 245 } 246 return s.params[paramNumber].name, nil 247 }