├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── LICENSE.md ├── README.md ├── mkcheck ├── fd.cpp ├── fd.h ├── mkcheck.cpp ├── proc.cpp ├── proc.h ├── syscall.cpp ├── syscall.h ├── trace.cpp ├── trace.h ├── util.cpp └── util.h ├── test ├── make │ ├── Makefile │ ├── a.c │ ├── a.h │ ├── b.c │ ├── b.h │ ├── c.c │ ├── c.h │ ├── d.h │ ├── lib_a │ │ ├── lib_a.c │ │ └── lib_a.h │ ├── lib_b │ │ ├── lib_b.c │ │ └── lib_b.h │ └── main.c └── parallel │ ├── Makefile │ ├── a.in │ ├── b.in │ ├── c.in │ ├── main.cpp │ └── out │ ├── a.cpp │ ├── a.h │ ├── b.cpp │ ├── b.h │ ├── c.cpp │ ├── c.h │ └── main └── tools └── fuzz_test ├── __main__.py ├── graph.py ├── mtime.py └── proc.py /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *.o 3 | test/make/out 4 | test/out 5 | tmp 6 | graph 7 | *.pyc 8 | tools/viewer/package.lock.json 9 | tools/viewer/dist/bundle.js 10 | tools/viewer/dist/data 11 | node_modules 12 | *.swp 13 | .watchmanconfig 14 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of the mkcheck project. 2 | # Licensing information can be found in the LICENSE file. 3 | # (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | cmake_minimum_required(VERSION 3.5) 6 | project(mkcheck CXX) 7 | 8 | find_package(Boost COMPONENTS system filesystem program_options REQUIRED) 9 | 10 | add_compile_options(-std=c++17) 11 | 12 | add_executable(mkcheck 13 | mkcheck/fd.cpp 14 | mkcheck/mkcheck.cpp 15 | mkcheck/syscall.cpp 16 | mkcheck/proc.cpp 17 | mkcheck/trace.cpp 18 | mkcheck/util.cpp 19 | ) 20 | target_link_libraries(mkcheck 21 | ${Boost_LIBRARIES} 22 | ) 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Licker Nandor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Nandor Licker 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mkcheck 2 | 3 | Incremental Build Verification 4 | 5 | # Supported Platforms 6 | 7 | Due to use of the ```ptrace``` system call, mkcheck only supports Linux-based operating systems at this time. 8 | Python 2 is required for fuzz testing. ```mkcheck``` only compiles with clang at this time. 9 | 10 | # Building 11 | 12 | ``` 13 | mkdir build 14 | cd build 15 | cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=clang++ 16 | make 17 | ``` 18 | 19 | # Running tests 20 | 21 | ``` 22 | make -C tests/make test 23 | make -C tests/parallel test 24 | ``` 25 | 26 | ## Dependency Graph Inference 27 | 28 | The dependency graph inference tool runs a child process under ptrace and dumps information about all files and processes involved in a build to a database file, specified using the ```--output``` flag. The file is in JSON format and is read by the fuzz testing tool. More information about the format is available in ```tools/fuzz_test/graph.py```. 29 | 30 | ``` 31 | mkcheck --output= -- 32 | ``` 33 | 34 | ## Build Fuzzing 35 | 36 | Fuzz testing is performed using a Python script, executed out of the directory of the project under test. The dependency graph must be inferred beforehand and stored at a path accesible to the fuzzing tool. 37 | `` must point to the `tools/fuzz_test` directory of the project. 38 | 39 | ``` 40 | cd 41 | python --graph-path= fuzz|list|query|build|race 42 | ``` 43 | 44 | Other options to the fuzzing script are: 45 | * ```--rule-path``` specifies the path to a YAML file containing regexes to filter out files. The document should be a map from the keys ```"filter_in"```, ```"filter_out"```, ```"filter_tmp"``` to lists of regexes which blacklist irrelevant inputs, outputs and files considered for race testing. 46 | * ```--use-hash``` forces the use of content hashing. 47 | * ```--argv``` specifies additional arguments to GNU Make projects. 48 | 49 | The fuzzing script supports the following commands: 50 | 51 | * _fuzz_ perform fuzz testing on the project. If only a few files are to be tested, their paths can be passed as optional arguments. If no files are specified, all unfiltered files are considered. 52 | * _list_ list the files which are considered by default 53 | * _query_ query the dependencies of a file from the graph 54 | * _build_ run a build and generate the graph, overwriting it 55 | * _race_ perform race testing 56 | 57 | # License 58 | 59 | The source code of this project is available under the MIT License. 60 | -------------------------------------------------------------------------------- /mkcheck/fd.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #include "fd.h" 6 | 7 | 8 | 9 | // ----------------------------------------------------------------------------- 10 | FDInfo::FDInfo() 11 | : FDInfo(0, "/dev/null", false) 12 | { 13 | } 14 | 15 | // ----------------------------------------------------------------------------- 16 | FDInfo::FDInfo(int fd, const fs::path &path, bool closeExec) 17 | : Fd(fd) 18 | , Path(path) 19 | , CloseExec(closeExec) 20 | , Closed(false) 21 | { 22 | } 23 | 24 | // ----------------------------------------------------------------------------- 25 | FDInfo::~FDInfo() 26 | { 27 | } 28 | -------------------------------------------------------------------------------- /mkcheck/fd.h: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | namespace fs = boost::filesystem; 11 | 12 | 13 | 14 | /** 15 | * File descriptor information. 16 | */ 17 | struct FDInfo final { 18 | /// Descriptor number. 19 | int Fd; 20 | /// Path to file. 21 | fs::path Path; 22 | /// Close-On-Exec flag. 23 | bool CloseExec; 24 | /// Flag indicating if the FD was closed or not. 25 | bool Closed; 26 | 27 | /// Constructs an empty file descriptor. 28 | FDInfo(); 29 | 30 | /// Constructs a file descriptor info object. 31 | FDInfo(int fd, const fs::path &path, bool closeExec); 32 | 33 | /// Destroys the structure. 34 | ~FDInfo(); 35 | }; 36 | 37 | 38 | 39 | /** 40 | * Map of inherited file descriptors. 41 | */ 42 | typedef std::vector FDSet; 43 | -------------------------------------------------------------------------------- /mkcheck/mkcheck.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "syscall.h" 28 | #include "trace.h" 29 | #include "util.h" 30 | 31 | 32 | 33 | // ----------------------------------------------------------------------------- 34 | class ProcessState final { 35 | public: 36 | /// Initialises the process state. 37 | ProcessState(pid_t pid) 38 | : pid_(pid) 39 | , entering_(false) 40 | { 41 | } 42 | 43 | /// Indicates if we're exiting/exiting the syscall. 44 | bool IsExiting() const { return !entering_; } 45 | 46 | /// Returns the syscall number. 47 | int64_t GetSyscall() const { return syscall_; } 48 | 49 | /// Returns the return value. 50 | int64_t GetReturn() const { return return_; } 51 | 52 | /// Returns a specific argument. 53 | uint64_t GetArg(size_t idx) const 54 | { 55 | assert(idx < kSyscallArgs); 56 | return args_[idx]; 57 | } 58 | 59 | /// Returns the arguments. 60 | Args GetArgs() const 61 | { 62 | Args args; 63 | args.PID = pid_; 64 | args.Return = return_; 65 | for (size_t i = 0; i < kSyscallArgs; ++i) { 66 | args.Arg[i] = args_[i]; 67 | } 68 | return args; 69 | } 70 | 71 | /// Extracts information from a register set. 72 | void Read(const struct user_regs_struct *regs) 73 | { 74 | if (entering_) { 75 | return_ = regs->rax; 76 | entering_ = false; 77 | } else { 78 | syscall_ = regs->orig_rax; 79 | args_[0] = regs->rdi; 80 | args_[1] = regs->rsi; 81 | args_[2] = regs->rdx; 82 | args_[3] = regs->r10; 83 | args_[4] = regs->r8; 84 | args_[5] = regs->r9; 85 | entering_ = true; 86 | } 87 | } 88 | 89 | /// Sets the name of the executable. 90 | void SetExecutable(const std::string &exec) 91 | { 92 | exec_ = exec; 93 | } 94 | 95 | /// Returns the name of the executable. 96 | std::string GetExecutable() const 97 | { 98 | return exec_; 99 | } 100 | 101 | private: 102 | /// PID of the traced process. 103 | pid_t pid_; 104 | /// Flag to indicate if the syscall is entered/exited. 105 | bool entering_; 106 | /// Name of the executable. 107 | std::string exec_; 108 | /// List of arguments. 109 | uint64_t args_[kSyscallArgs]; 110 | /// Return value. 111 | int64_t return_; 112 | /// Syscall number. 113 | int64_t syscall_; 114 | }; 115 | 116 | 117 | // ----------------------------------------------------------------------------- 118 | bool IsExecutable(const fs::path &path) 119 | { 120 | struct stat st; 121 | 122 | // Check if file exists. 123 | if (stat(path.string().c_str(), &st) != 0) { 124 | return false; 125 | } 126 | // Ensure it is a file. 127 | if (!S_ISREG(st.st_mode)) { 128 | return false; 129 | } 130 | // Check if it is user-executable. 131 | if (!(st.st_mode & S_IXUSR)) { 132 | return false; 133 | } 134 | return true; 135 | } 136 | 137 | // ----------------------------------------------------------------------------- 138 | std::string FindExecutable(const std::string &exec) 139 | { 140 | fs::path candidate = fs::absolute(exec); 141 | if (IsExecutable(candidate)) { 142 | return candidate.string(); 143 | } 144 | 145 | const char *colon; 146 | const char *ptr = getenv("PATH"); 147 | do { 148 | colon = strchr(ptr, ':'); 149 | 150 | const fs::path path = std::string(ptr, colon ? colon - ptr : strlen(ptr)); 151 | 152 | candidate = path / exec; 153 | if (IsExecutable(candidate)) { 154 | return candidate.string(); 155 | } 156 | ptr = colon + 1; 157 | } while (colon); 158 | 159 | throw std::runtime_error("Cannot find executable: " + exec); 160 | } 161 | 162 | // ----------------------------------------------------------------------------- 163 | int RunChild(const std::string &exec, const std::vector &args) 164 | { 165 | ptrace(PTRACE_TRACEME); 166 | raise(SIGSTOP); 167 | return execvp(exec.c_str(), args.data()); 168 | } 169 | 170 | // ----------------------------------------------------------------------------- 171 | static constexpr int kTraceOptions 172 | = PTRACE_O_TRACESYSGOOD 173 | | PTRACE_O_TRACECLONE 174 | | PTRACE_O_TRACEFORK 175 | | PTRACE_O_TRACEVFORK 176 | | PTRACE_O_EXITKILL 177 | ; 178 | 179 | // ----------------------------------------------------------------------------- 180 | int RunTracer(const fs::path &output, pid_t root) 181 | { 182 | // Trace context. 183 | auto trace = std::make_unique(); 184 | 185 | // Skip the first signal, which is SIGSTOP. 186 | int status; 187 | waitpid(root, &status, 0); 188 | ptrace(PTRACE_SETOPTIONS, root, nullptr, kTraceOptions); 189 | trace->SpawnTrace(0, root); 190 | 191 | // Set of tracked processses. 192 | std::unordered_map> tracked; 193 | tracked[root] = std::make_shared(root); 194 | 195 | auto GetState = [&tracked](pid_t pid) { 196 | auto it = tracked.find(pid); 197 | if (it == tracked.end()) { 198 | throw std::runtime_error("Invalid PID: " + std::to_string(pid)); 199 | } 200 | return it->second; 201 | }; 202 | 203 | // Processes waiting to be started. 204 | std::set candidates; 205 | 206 | // Process to wait for - after clone/vfork/exec, the callee is given 207 | // priority in order to trap and set up the child's data structure. 208 | // Otherwise, it is -1, stopping the first available child. 209 | pid_t waitFor = -1; 210 | 211 | // Keep tracking syscalls while any process in the hierarchy is running. 212 | int restartSig = 0; 213 | pid_t pid = root; 214 | while (!tracked.empty()) { 215 | // Trap a child on the next syscall. 216 | if (pid > 0) { 217 | if (ptrace(PTRACE_SYSCALL, pid, 0, restartSig) < 0) { 218 | throw std::runtime_error("ptrace failed"); 219 | } 220 | } 221 | 222 | // Wait for a child or any children to stop. 223 | if ((pid = waitpid(waitFor, &status, __WALL)) < 0) { 224 | throw std::runtime_error("waitpid failed"); 225 | } 226 | waitFor = -1; 227 | 228 | // The root process must exit with 0. 229 | if (WIFEXITED(status) && pid == root) { 230 | const int code = WEXITSTATUS(status); 231 | if (code != 0) { 232 | throw std::runtime_error("non-zero exit " + std::to_string(code) 233 | ); 234 | } 235 | } 236 | 237 | // The root process should not exit with a signal. 238 | if (WIFSIGNALED(status) && pid == root) { 239 | const int signo = WTERMSIG(status); 240 | throw std::runtime_error("killed by signal " + std::to_string(signo)); 241 | } 242 | 243 | // Remove the process from the tracked ones on exit. 244 | if (WIFEXITED(status) || WIFSIGNALED(status)) { 245 | trace->EndTrace(pid); 246 | tracked.erase(pid); 247 | pid = -1; 248 | continue; 249 | } 250 | 251 | /// Handle signals dispatched to children. 252 | switch (int sig = WSTOPSIG(status)) { 253 | case SIGTRAP | 0x80: { 254 | // By setting PTRACE_O_TRACESYSGOOD, bit 7 of the system call 255 | // number is set in order to distinguish system call traps 256 | // from other traps. 257 | restartSig = 0; 258 | break; 259 | } 260 | case SIGTRAP: { 261 | // SIGTRAP is sent with an event number in certain scenarios. 262 | // Simply restart the process with signal number 0. 263 | const int event = status >> 16; 264 | switch (event) { 265 | case PTRACE_EVENT_FORK: 266 | case PTRACE_EVENT_VFORK: 267 | case PTRACE_EVENT_CLONE: { 268 | bool shareVM = false; 269 | if (event == PTRACE_EVENT_CLONE) { 270 | shareVM = GetState(pid)->GetArg(2) & CLONE_VM; 271 | } 272 | 273 | // Get the ID of the child process. 274 | pid_t child; 275 | ptrace(PTRACE_GETEVENTMSG, pid, 0, &child); 276 | 277 | // Set tracing options for the child. 278 | ptrace(PTRACE_SETOPTIONS, pid, nullptr, kTraceOptions); 279 | 280 | // Create an object tracking the process, if one does not exist yet. 281 | tracked.emplace(child, std::make_shared(child)); 282 | 283 | if (shareVM) { 284 | // Shared the parent's structure with the child thread. 285 | trace->ShareTrace(pid, child); 286 | } else { 287 | // Spawn a new tracking state for the child. 288 | trace->SpawnTrace(pid, child); 289 | } 290 | 291 | // Start tracking it. 292 | candidates.insert(child); 293 | restartSig = 0; 294 | break; 295 | } 296 | default: { 297 | break; 298 | } 299 | } 300 | restartSig = 0; 301 | continue; 302 | } 303 | case SIGSTOP: { 304 | auto it = candidates.find(pid); 305 | if (it != candidates.end()) { 306 | // The first SIGSTOP is ignored. 307 | candidates.erase(it); 308 | restartSig = 0; 309 | } else { 310 | restartSig = SIGSTOP; 311 | } 312 | continue; 313 | } 314 | default: { 315 | // Deliver other signals to the process. 316 | restartSig = sig; 317 | continue; 318 | } 319 | } 320 | 321 | // Fetch the state desribing the process. 322 | std::shared_ptr state = GetState(pid); 323 | 324 | // Read syscall arguments on entry & the return value on exit. 325 | struct user_regs_struct regs; 326 | ptrace(PTRACE_GETREGS, pid, 0, ®s); 327 | state->Read(®s); 328 | 329 | // Handle the system call. 330 | auto sno = state->GetSyscall(); 331 | switch (sno) { 332 | case SYS_execve: { 333 | // Execve is special since its arguments can't be read once the 334 | // process image is replaced, thus the argument is read on entry. 335 | if (state->IsExiting()) { 336 | if (state->GetReturn() >= 0) { 337 | trace->StartTrace(pid, state->GetExecutable()); 338 | } 339 | } else { 340 | state->SetExecutable(ReadString(pid, state->GetArg(0))); 341 | } 342 | break; 343 | } 344 | case SYS_vfork: 345 | case SYS_fork: 346 | case SYS_clone: { 347 | // Try to wait for the exit event of this sycall before any others. 348 | waitFor = state->IsExiting() ? -1 : pid; 349 | break; 350 | } 351 | } 352 | 353 | // All other system calls are handled on exit. 354 | if (state->IsExiting()) { 355 | Handle(trace.get(), sno, state->GetArgs()); 356 | } 357 | } 358 | 359 | trace->Dump(output); 360 | return EXIT_SUCCESS; 361 | } 362 | 363 | // ----------------------------------------------------------------------------- 364 | static struct option kOptions[] = 365 | { 366 | { "output", required_argument, 0, 'o' }, 367 | }; 368 | 369 | // ----------------------------------------------------------------------------- 370 | int main(int argc, char **argv) 371 | { 372 | // Parse arguments. 373 | std::string output; 374 | std::string exec; 375 | std::vector args; 376 | { 377 | int c = 0, idx = 0; 378 | while (c >= 0) { 379 | switch (c = getopt_long(argc, argv, "o:", kOptions, &idx)) { 380 | case -1: { 381 | break; 382 | } 383 | case 'o': { 384 | output = optarg; 385 | continue; 386 | } 387 | default: { 388 | std::cerr << "Unknown option." << std::endl; 389 | return EXIT_FAILURE; 390 | } 391 | } 392 | } 393 | 394 | if (output.empty()) { 395 | std::cerr << "Missing output directory." << std::endl; 396 | return EXIT_FAILURE; 397 | } 398 | if (optind == argc) { 399 | std::cerr << "Missing executable." << std::endl; 400 | return EXIT_FAILURE; 401 | } 402 | 403 | for (int i = optind; i < argc; ++i) { 404 | args.push_back(argv[i]); 405 | } 406 | args.push_back(nullptr); 407 | exec = FindExecutable(args[0]); 408 | } 409 | 410 | // Fork & start tracing. 411 | switch (pid_t pid = fork()) { 412 | case -1: { 413 | return EXIT_FAILURE; 414 | } 415 | case 0: { 416 | return RunChild(exec, args); 417 | } 418 | default: { 419 | try { 420 | return RunTracer(output, pid); 421 | } catch (const std::exception &ex) { 422 | std::cerr << "[Exception] " << ex.what() << std::endl; 423 | return EXIT_FAILURE; 424 | } 425 | } 426 | } 427 | 428 | return EXIT_SUCCESS; 429 | } 430 | -------------------------------------------------------------------------------- /mkcheck/proc.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #include 6 | 7 | #include "proc.h" 8 | #include "trace.h" 9 | 10 | 11 | 12 | // ----------------------------------------------------------------------------- 13 | Process::Process( 14 | Trace *trace, 15 | pid_t pid, 16 | uint64_t parent, 17 | uint64_t uid, 18 | uint64_t image, 19 | const FDSet &fdSet, 20 | const fs::path &cwd, 21 | bool isCOW) 22 | : trace_(trace) 23 | , pid_(pid) 24 | , parent_(parent) 25 | , uid_(uid) 26 | , image_(image) 27 | , cwd_(cwd) 28 | , isCOW_(isCOW) 29 | , pipeCount_(0) 30 | { 31 | inputs_.insert(image); 32 | for (const auto &fd : fdSet) { 33 | files_.emplace(fd.Fd, fd); 34 | } 35 | } 36 | 37 | // ----------------------------------------------------------------------------- 38 | Process::~Process() 39 | { 40 | } 41 | 42 | // ----------------------------------------------------------------------------- 43 | void Process::Dump(std::ostream &os) 44 | { 45 | os << "{" << std::endl; 46 | os << " \"uid\": " << uid_ << "," << std::endl; 47 | os << " \"parent\": " << parent_ << "," << std::endl; 48 | os << " \"image\": " << image_ << "," << std::endl; 49 | 50 | if (isCOW_) { 51 | os << " \"cow\": true,"; 52 | } 53 | 54 | // Dump output files. 55 | os << " \"output\": ["; 56 | for (auto it = outputs_.begin(); it != outputs_.end();) { 57 | os << *it; 58 | if (++it != outputs_.end()) { 59 | os << ","; 60 | } 61 | } 62 | os << "]," << std::endl; 63 | 64 | // Dump input files. 65 | os << " \"input\": ["; 66 | for (auto it = inputs_.begin(); it != inputs_.end();) { 67 | os << *it; 68 | if (++it != inputs_.end()) { 69 | os << ","; 70 | } 71 | } 72 | os << "]" << std::endl; 73 | 74 | os << "}"; 75 | } 76 | 77 | // ----------------------------------------------------------------------------- 78 | fs::path Process::Normalise(const fs::path &path) 79 | { 80 | return Normalise(AT_FDCWD, path, cwd_); 81 | } 82 | 83 | // ----------------------------------------------------------------------------- 84 | fs::path Process::Normalise(const fs::path &path, const fs::path &cwd) 85 | { 86 | return Normalise(AT_FDCWD, path, cwd); 87 | } 88 | 89 | // ----------------------------------------------------------------------------- 90 | fs::path Process::Normalise(int fd, const fs::path &path) 91 | { 92 | return Normalise(fd, path, cwd_); 93 | } 94 | 95 | // ----------------------------------------------------------------------------- 96 | fs::path Process::Normalise(int fd, const fs::path &path, const fs::path &cwd) 97 | { 98 | boost::system::error_code ec; 99 | 100 | // Get rid of the relative path. 101 | fs::path fullPath; 102 | if (path.is_relative()) { 103 | if (fd == AT_FDCWD) { 104 | fullPath = (cwd / path).normalize(); 105 | } else { 106 | return (GetFd(fd) / path).normalize(); 107 | } 108 | } else { 109 | fullPath = path; 110 | } 111 | 112 | // If the file exists, return the canonical path. 113 | const fs::path canonical = fs::canonical(fullPath, ec); 114 | if (!ec) { 115 | return canonical; 116 | } 117 | 118 | // If the file was deleted, try to canonicalise the parent. 119 | const fs::path parent = fullPath.parent_path(); 120 | const fs::path file = fullPath.filename(); 121 | 122 | const fs::path canonicalParent = fs::canonical(parent, ec); 123 | if (!ec) { 124 | return canonicalParent / file; 125 | } 126 | 127 | return fullPath; 128 | } 129 | 130 | // ----------------------------------------------------------------------------- 131 | void Process::AddInput(const fs::path &path) 132 | { 133 | inputs_.insert(trace_->Find(path)); 134 | } 135 | 136 | // ----------------------------------------------------------------------------- 137 | void Process::AddOutput(const fs::path &path) 138 | { 139 | outputs_.insert(trace_->Find(path)); 140 | AddDestination(path); 141 | trace_->Create(path); 142 | } 143 | 144 | // ----------------------------------------------------------------------------- 145 | void Process::AddDestination(const fs::path &path) 146 | { 147 | uint64_t parent = trace_->Find(path.parent_path()); 148 | if (outputs_.find(parent) == outputs_.end()) { 149 | inputs_.insert(parent); 150 | } 151 | } 152 | 153 | // ----------------------------------------------------------------------------- 154 | void Process::Remove(const fs::path &path) 155 | { 156 | trace_->Unlink(Normalise(path)); 157 | } 158 | 159 | // ----------------------------------------------------------------------------- 160 | void Process::Rename(const fs::path &from, const fs::path &to) 161 | { 162 | trace_->Unlink(from); 163 | trace_->AddDependency(from, to); 164 | AddOutput(to); 165 | } 166 | 167 | // ----------------------------------------------------------------------------- 168 | void Process::Link(const fs::path &target, const fs::path &linkpath) 169 | { 170 | trace_->AddDependency(target, linkpath); 171 | trace_->Create(linkpath); 172 | AddOutput(linkpath); 173 | } 174 | 175 | // ----------------------------------------------------------------------------- 176 | void Process::MapFd(int fd, const fs::path &path) 177 | { 178 | FDInfo info(fd, path, false); 179 | 180 | auto it = files_.find(fd); 181 | if (it == files_.end()) { 182 | files_.emplace(fd, info); 183 | } else { 184 | it->second = info; 185 | } 186 | } 187 | 188 | // ----------------------------------------------------------------------------- 189 | fs::path Process::GetFd(int fd) 190 | { 191 | auto it = files_.find(fd); 192 | if (it == files_.end()) { 193 | throw std::runtime_error( 194 | "Unknown file descriptor: " + std::to_string(fd) 195 | ); 196 | } 197 | return it->second.Path; 198 | } 199 | 200 | // ----------------------------------------------------------------------------- 201 | void Process::DupFd(int from, int to) 202 | { 203 | auto it = files_.find(from); 204 | if (it == files_.end()) { 205 | throw std::runtime_error( 206 | "Unknown file descriptor: " + std::to_string(from) 207 | ); 208 | } 209 | 210 | const auto &oldInfo = it->second; 211 | FDInfo info(to, oldInfo.Path, false); 212 | 213 | auto jt = files_.find(to); 214 | if (jt == files_.end()) { 215 | files_.emplace(to, info); 216 | } else { 217 | jt->second = info; 218 | } 219 | } 220 | 221 | // ----------------------------------------------------------------------------- 222 | void Process::Pipe(int rd, int wr) 223 | { 224 | const fs::path path = "/proc/" + std::to_string(pid_) + "/pipes/"; 225 | const auto pipeRd = path / std::to_string(pipeCount_) / std::to_string(rd); 226 | const auto pipeWr = path / std::to_string(pipeCount_) / std::to_string(wr); 227 | pipeCount_ += 1; 228 | 229 | trace_->AddDependency(pipeWr, pipeRd); 230 | MapFd(rd, pipeRd); 231 | MapFd(wr, pipeWr); 232 | } 233 | 234 | // ----------------------------------------------------------------------------- 235 | void Process::CloseFd(int fd) 236 | { 237 | auto it = files_.find(fd); 238 | if (it != files_.end()) { 239 | it->second.Closed = true; 240 | } 241 | } 242 | 243 | // ----------------------------------------------------------------------------- 244 | void Process::SetCloseExec(int fd, bool closeExec) 245 | { 246 | auto it = files_.find(fd); 247 | if (it == files_.end()) { 248 | throw std::runtime_error( 249 | "Unknown file descriptor: " + std::to_string(fd) 250 | ); 251 | } 252 | it->second.CloseExec = closeExec; 253 | } 254 | 255 | // ----------------------------------------------------------------------------- 256 | FDSet Process::GetAllFDs() 257 | { 258 | FDSet fdSet; 259 | for (const auto &file : files_) { 260 | fdSet.push_back(file.second); 261 | } 262 | return fdSet; 263 | } 264 | 265 | // ----------------------------------------------------------------------------- 266 | FDSet Process::GetInheritedFDs() 267 | { 268 | FDSet fdSet; 269 | for (const auto &file : files_) { 270 | const auto &info = file.second; 271 | if (!info.CloseExec && !info.Closed) { 272 | fdSet.push_back(info); 273 | } 274 | } 275 | return fdSet; 276 | } 277 | -------------------------------------------------------------------------------- /mkcheck/proc.h: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "fd.h" 12 | 13 | class Trace; 14 | 15 | 16 | /** 17 | * Process in a trace. 18 | */ 19 | class Process final { 20 | public: 21 | /// Creates a new trace with a given image. 22 | Process( 23 | Trace *trace, 24 | pid_t pid, 25 | uint64_t parent, 26 | uint64_t uid, 27 | uint64_t image, 28 | const FDSet &fdSet, 29 | const fs::path &cwd, 30 | bool isCOW); 31 | 32 | /// Cleanup. 33 | ~Process(); 34 | 35 | /// Dumps a process to a stream. 36 | void Dump(std::ostream &os); 37 | 38 | /// Returns the parent UID. 39 | uint64_t GetParent() const { return parent_; } 40 | /// Returns the process UID. 41 | uint64_t GetUID() const { return uid_; } 42 | /// Returns the name of the image. 43 | uint64_t GetImage() const { return image_; } 44 | /// Returns the working directory. 45 | fs::path GetCwd() const { return cwd_; } 46 | 47 | /// Checks if the process is Copy-On-Write. 48 | bool IsCOW() const { return isCOW_; } 49 | 50 | /// Resolves a path, relative to the cwd. 51 | fs::path Normalise(const fs::path &path); 52 | /// Resolves a path, given cwd. 53 | fs::path Normalise(const fs::path &path, const fs::path &cwd); 54 | /// Resolves a path, relative to any directory. 55 | fs::path Normalise(int fd, const fs::path &path); 56 | /// Resolves a path, relative to any directory, given a cwd. 57 | fs::path Normalise(int fd, const fs::path &path, const fs::path &cwd); 58 | 59 | /// Adds a touched (stat'd) file to a process. 60 | void AddTouched(const fs::path &path) { AddInput(path); } 61 | /// Adds an input file to a process. 62 | void AddInput(const fs::path &path); 63 | /// Adds an output file to a process. 64 | void AddOutput(const fs::path &path); 65 | 66 | /// Adds a touched file to a process. 67 | void AddTouched(int fd) { AddInput(fd); } 68 | /// Adds an input fd to a process. 69 | void AddInput(int fd) { AddInput(GetFd(fd)); } 70 | /// Adds an output fd to a process. 71 | void AddOutput(int fd) { AddOutput(GetFd(fd)); } 72 | 73 | /// Adds a destination path as an input. 74 | void AddDestination(const fs::path &path); 75 | /// Sets the working directory. 76 | void SetCwd(const fs::path &cwd) { cwd_ = cwd; } 77 | /// Unlinks a file or directory. 78 | void Remove(const fs::path &path); 79 | /// Renames a file. 80 | void Rename(const fs::path &from, const fs::path &to); 81 | /// Creates a symlink. 82 | void Link(const fs::path &target, const fs::path &linkpath); 83 | 84 | /// Maps a file descriptor to a path. 85 | void MapFd(int fd, const fs::path &path); 86 | /// Returns the path to a file opened by a descriptor. 87 | fs::path GetFd(int fd); 88 | /// Duplicates a file descriptor. 89 | void DupFd(int from, int to); 90 | /// Sets up a pipe. 91 | void Pipe(int rd, int wr); 92 | /// Closes a file descriptor. 93 | void CloseFd(int fd); 94 | /// Adds a file descriptor to the cloexec set. 95 | void SetCloseExec(int fd, bool closeExec); 96 | 97 | /// Returns all file descriptors. 98 | FDSet GetAllFDs(); 99 | /// Returns the set of non-cloexec fds. 100 | FDSet GetInheritedFDs(); 101 | 102 | private: 103 | /// Pointer to the trace. 104 | Trace *trace_; 105 | /// Process identifier. 106 | const pid_t pid_; 107 | /// Parent identifier. 108 | const uint64_t parent_; 109 | /// Unique instance identifier. 110 | const uint64_t uid_; 111 | /// Name of the image. 112 | const uint64_t image_; 113 | /// Working directory. 114 | fs::path cwd_; 115 | /// If image is copy-on-write. 116 | bool isCOW_; 117 | 118 | /// Open files. 119 | std::unordered_map files_; 120 | /// Input files. 121 | std::set inputs_; 122 | /// Output files. 123 | std::set outputs_; 124 | 125 | /// Number of pipes, used to generate unique names. 126 | size_t pipeCount_; 127 | }; 128 | -------------------------------------------------------------------------------- /mkcheck/syscall.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #include "syscall.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "proc.h" 20 | #include "trace.h" 21 | #include "util.h" 22 | 23 | 24 | 25 | // ----------------------------------------------------------------------------- 26 | static void sys_read(Process *proc, const Args &args) 27 | { 28 | if (args.Return >= 0) { 29 | proc->AddInput(args[0]); 30 | } 31 | } 32 | 33 | // ----------------------------------------------------------------------------- 34 | static void sys_write(Process *proc, const Args &args) 35 | { 36 | if (args.Return >= 0) { 37 | proc->AddOutput(args[0]); 38 | } 39 | } 40 | 41 | // ----------------------------------------------------------------------------- 42 | static void sys_open(Process *proc, const Args &args) 43 | { 44 | const fs::path path = proc->Normalise(ReadString(args.PID, args[0])); 45 | const uint64_t flags = args[1]; 46 | const int fd = args.Return; 47 | 48 | if (args.Return >= 0) { 49 | proc->MapFd(fd, path); 50 | proc->SetCloseExec(fd, flags & O_CLOEXEC); 51 | } 52 | } 53 | 54 | // ----------------------------------------------------------------------------- 55 | static void sys_close(Process *proc, const Args &args) 56 | { 57 | if (args.Return >= 0) { 58 | proc->CloseFd(args[0]); 59 | } 60 | } 61 | 62 | // ----------------------------------------------------------------------------- 63 | static void sys_stat(Process *proc, const Args &args) 64 | { 65 | const fs::path path = proc->Normalise(ReadString(args.PID, args[0])); 66 | if (args.Return >= 0) { 67 | proc->AddTouched(path); 68 | } 69 | } 70 | 71 | // ----------------------------------------------------------------------------- 72 | static void sys_fstat(Process *proc, const Args &args) 73 | { 74 | if (args.Return >= 0) { 75 | proc->AddTouched(args[0]); 76 | } 77 | } 78 | 79 | // ----------------------------------------------------------------------------- 80 | static void sys_lstat(Process *proc, const Args &args) 81 | { 82 | const fs::path path = proc->Normalise(ReadString(args.PID, args[0])); 83 | 84 | if (args.Return >= 0) { 85 | proc->AddTouched(path); 86 | } 87 | } 88 | 89 | // ----------------------------------------------------------------------------- 90 | static void sys_mmap(Process *proc, const Args &args) 91 | { 92 | const int prot = args[2]; 93 | const int flags = args[3]; 94 | const int fd = args[4]; 95 | 96 | if (args.Return != MAP_ANON && fd != -1) { 97 | // Writes are only carried out to the file in shared, writable mappings. 98 | if ((flags & MAP_SHARED) && (prot & PROT_WRITE)) { 99 | proc->AddOutput(fd); 100 | } else { 101 | proc->AddInput(fd); 102 | } 103 | } 104 | } 105 | 106 | // ----------------------------------------------------------------------------- 107 | static void sys_pread64(Process *proc, const Args &args) 108 | { 109 | if (args.Return >= 0) { 110 | proc->AddInput(args[0]); 111 | } 112 | } 113 | 114 | // ----------------------------------------------------------------------------- 115 | static void sys_readv(Process *proc, const Args &args) 116 | { 117 | if (args.Return >= 0) { 118 | proc->AddInput(args[0]); 119 | } 120 | } 121 | 122 | // ----------------------------------------------------------------------------- 123 | static void sys_writev(Process *proc, const Args &args) 124 | { 125 | if (args.Return >= 0) { 126 | proc->AddInput(args[0]); 127 | } 128 | } 129 | 130 | // ----------------------------------------------------------------------------- 131 | static void sys_access(Process *proc, const Args &args) 132 | { 133 | const fs::path path = proc->Normalise(ReadString(args.PID, args[0])); 134 | 135 | if (args.Return >= 0) { 136 | proc->AddTouched(path); 137 | } 138 | } 139 | 140 | // ----------------------------------------------------------------------------- 141 | static void sys_pipe(Process *proc, const Args &args) 142 | { 143 | int fds[2]; 144 | ReadBuffer(args.PID, fds, args[0], 2 * sizeof(int)); 145 | if (args.Return >= 0) { 146 | proc->Pipe(fds[0], fds[1]); 147 | } 148 | } 149 | 150 | // ----------------------------------------------------------------------------- 151 | static void sys_dup(Process *proc, const Args &args) 152 | { 153 | if (args.Return >= 0) { 154 | proc->DupFd(args[0], args.Return); 155 | } 156 | } 157 | 158 | // ----------------------------------------------------------------------------- 159 | static void sys_dup2(Process *proc, const Args &args) 160 | { 161 | if (args.Return >= 0) { 162 | proc->DupFd(args[0], args.Return); 163 | } 164 | } 165 | 166 | // ----------------------------------------------------------------------------- 167 | static void sys_socket(Process *proc, const Args &args) 168 | { 169 | if (args.Return >= 0) { 170 | proc->MapFd(args.Return, "/proc/network"); 171 | } 172 | } 173 | 174 | // ----------------------------------------------------------------------------- 175 | static void sys_fcntl(Process *proc, const Args &args) 176 | { 177 | const int fd = args[0]; 178 | const int cmd = args[1]; 179 | 180 | if (args.Return >= 0) { 181 | switch (cmd) { 182 | case F_DUPFD: { 183 | proc->DupFd(args[0], args.Return); 184 | break; 185 | } 186 | case F_DUPFD_CLOEXEC: { 187 | proc->DupFd(args[0], args.Return); 188 | proc->SetCloseExec(args.Return, false); 189 | break; 190 | } 191 | case F_SETFD: { 192 | const int arg = args[2]; 193 | proc->SetCloseExec(fd, arg & FD_CLOEXEC); 194 | break; 195 | } 196 | case F_GETFD: 197 | case F_GETFL: 198 | case F_SETFL: { 199 | break; 200 | } 201 | case F_GETLK: 202 | case F_SETLK: 203 | case F_SETLKW: { 204 | break; 205 | } 206 | case F_OFD_GETLK: 207 | case F_OFD_SETLK: 208 | case F_OFD_SETLKW: { 209 | break; 210 | } 211 | default: { 212 | throw std::runtime_error( 213 | "Unknown fnctl (cmd = " + std::to_string(cmd) + ")" 214 | ); 215 | } 216 | } 217 | } 218 | } 219 | 220 | // ----------------------------------------------------------------------------- 221 | static void sys_ftruncate(Process *proc, const Args &args) 222 | { 223 | if (args.Return >= 0) { 224 | proc->AddOutput(args[0]); 225 | } 226 | } 227 | 228 | // ----------------------------------------------------------------------------- 229 | static void sys_getdents(Process *proc, const Args &args) 230 | { 231 | if (args.Return >= 0) { 232 | proc->AddInput(args[0]); 233 | } 234 | } 235 | 236 | // ----------------------------------------------------------------------------- 237 | static void sys_chdir(Process *proc, const Args &args) 238 | { 239 | const fs::path path = proc->Normalise(ReadString(args.PID, args[0])); 240 | 241 | if (args.Return >= 0) { 242 | proc->SetCwd(path); 243 | } 244 | } 245 | 246 | // ----------------------------------------------------------------------------- 247 | static void sys_fchdir(Process *proc, const Args &args) 248 | { 249 | const int fd = args[0]; 250 | if (args.Return >= 0) { 251 | proc->SetCwd(proc->GetFd(fd)); 252 | } 253 | } 254 | 255 | // ----------------------------------------------------------------------------- 256 | static void sys_rename(Process *proc, const Args &args) 257 | { 258 | const fs::path src = proc->Normalise(ReadString(args.PID, args[0])); 259 | const fs::path dst = proc->Normalise(ReadString(args.PID, args[1])); 260 | 261 | if (args.Return >= 0) { 262 | proc->Rename(src, dst); 263 | } 264 | } 265 | 266 | // ----------------------------------------------------------------------------- 267 | static void sys_mkdir(Process *proc, const Args &args) 268 | { 269 | const fs::path path = proc->Normalise(ReadString(args.PID, args[0])); 270 | 271 | if (args.Return >= 0) { 272 | proc->AddOutput(path); 273 | } 274 | } 275 | 276 | // ----------------------------------------------------------------------------- 277 | static void sys_rmdir(Process *proc, const Args &args) 278 | { 279 | const fs::path path = proc->Normalise(ReadString(args.PID, args[0])); 280 | 281 | if (args.Return >= 0) { 282 | proc->Remove(path); 283 | } 284 | } 285 | 286 | // ----------------------------------------------------------------------------- 287 | static void sys_link(Process *proc, const Args &args) 288 | { 289 | if (args.Return >= 0) { 290 | const fs::path srcRel = ReadString(args.PID, args[0]); 291 | const fs::path dstRel = ReadString(args.PID, args[1]); 292 | 293 | const fs::path src = proc->Normalise(srcRel); 294 | const fs::path dstParent = proc->Normalise(dstRel.parent_path()); 295 | 296 | proc->Link(src, dstParent / dstRel.filename()); 297 | } 298 | } 299 | 300 | // ----------------------------------------------------------------------------- 301 | static void sys_creat(Process *proc, const Args &args) 302 | { 303 | const fs::path path = proc->Normalise(ReadString(args.PID, args[0])); 304 | const uint64_t flags = args[1]; 305 | 306 | if (args.Return >= 0) { 307 | const int fd = args.Return; 308 | proc->MapFd(fd, path); 309 | proc->SetCloseExec(fd, flags & O_CLOEXEC); 310 | } 311 | } 312 | 313 | // ----------------------------------------------------------------------------- 314 | static void sys_unlink(Process *proc, const Args &args) 315 | { 316 | const fs::path path = proc->Normalise(ReadString(args.PID, args[0])); 317 | 318 | if (args.Return >= 0) { 319 | proc->Remove(path); 320 | } 321 | } 322 | 323 | // ----------------------------------------------------------------------------- 324 | static void sys_symlink(Process *proc, const Args &args) 325 | { 326 | if (args.Return >= 0) { 327 | const fs::path src = ReadString(args.PID, args[0]); 328 | const fs::path dst = ReadString(args.PID, args[1]); 329 | 330 | const fs::path parent = proc->Normalise(dst.parent_path()); 331 | const fs::path srcPath = proc->Normalise(src, parent); 332 | const fs::path dstPath = parent / dst.filename(); 333 | 334 | // configure seems to create links pointing to themselves, which we ignore. 335 | if (srcPath != dstPath) { 336 | proc->Link(srcPath, dstPath); 337 | } 338 | } 339 | } 340 | 341 | // ----------------------------------------------------------------------------- 342 | static void sys_readlink(Process *proc, const Args &args) 343 | { 344 | const fs::path path = proc->Normalise(ReadString(args.PID, args[0])); 345 | if (args.Return >= 0) { 346 | proc->AddInput(path); 347 | } 348 | } 349 | 350 | // ----------------------------------------------------------------------------- 351 | static void sys_utime(Process *proc, const Args &args) 352 | { 353 | if (args.Return >= 0) { 354 | proc->AddOutput(proc->Normalise(ReadString(args.PID, args[0]))); 355 | } 356 | } 357 | 358 | // ----------------------------------------------------------------------------- 359 | static void sys_linkat(Process *proc, const Args &args) 360 | { 361 | if (args.Return >= 0) { 362 | const fs::path srcRel = ReadString(args.PID, args[1]); 363 | const fs::path dstRel = ReadString(args.PID, args[3]); 364 | 365 | const fs::path src = proc->Normalise(args[0], srcRel); 366 | const fs::path dstParent = proc->Normalise(args[2], dstRel.parent_path()); 367 | 368 | proc->Link(src, dstParent / dstRel.filename()); 369 | } 370 | } 371 | 372 | // ----------------------------------------------------------------------------- 373 | static void sys_fsetxattr(Process *proc, const Args &args) 374 | { 375 | if (args.Return >= 0) { 376 | proc->AddOutput(proc->GetFd(args[0])); 377 | } 378 | } 379 | 380 | // ----------------------------------------------------------------------------- 381 | static void sys_getxattr(Process *proc, const Args &args) 382 | { 383 | const fs::path path = ReadString(args.PID, args[0]); 384 | const fs::path parent = proc->Normalise(path.parent_path()); 385 | if (args.Return >= 0) { 386 | proc->AddInput(parent / path.filename()); 387 | } 388 | } 389 | 390 | // ----------------------------------------------------------------------------- 391 | static void sys_lgetxattr(Process *proc, const Args &args) 392 | { 393 | const fs::path path = proc->Normalise(ReadString(args.PID, args[0])); 394 | if (args.Return >= 0) { 395 | proc->AddInput(path); 396 | } 397 | } 398 | 399 | // ----------------------------------------------------------------------------- 400 | static void sys_llistxattr(Process *proc, const Args &args) 401 | { 402 | const fs::path path = proc->Normalise(ReadString(args.PID, args[0])); 403 | if (args.Return >= 0) { 404 | proc->AddInput(path); 405 | } 406 | } 407 | 408 | // ----------------------------------------------------------------------------- 409 | static void sys_flistxattr(Process *proc, const Args &args) 410 | { 411 | throw std::runtime_error("not implemented"); 412 | } 413 | 414 | 415 | // ----------------------------------------------------------------------------- 416 | static void sys_epoll_create(Process *proc, const Args &args) 417 | { 418 | if (args.Return >= 0) { 419 | proc->MapFd(args.Return, "/proc/" + std::to_string(args.PID) + "/epoll"); 420 | } 421 | } 422 | 423 | // ----------------------------------------------------------------------------- 424 | static void sys_getdents64(Process *proc, const Args &args) 425 | { 426 | if (args.Return >= 0) { 427 | proc->AddInput(args[0]); 428 | } 429 | } 430 | 431 | // ----------------------------------------------------------------------------- 432 | static void sys_openat(Process *proc, const Args &args) 433 | { 434 | const int dirfd = args[0]; 435 | const fs::path path = proc->Normalise(dirfd, ReadString(args.PID, args[1])); 436 | const uint64_t flags = args[2]; 437 | if (args.Return >= 0) { 438 | const int fd = args.Return; 439 | proc->MapFd(fd, path); 440 | proc->SetCloseExec(fd, flags & O_CLOEXEC); 441 | } 442 | } 443 | 444 | // ----------------------------------------------------------------------------- 445 | static void sys_mkdirat(Process *proc, const Args &args) 446 | { 447 | const int dirfd = args[0]; 448 | const fs::path path = proc->Normalise(dirfd, ReadString(args.PID, args[1])); 449 | 450 | if (args.Return >= 0) { 451 | proc->AddOutput(path); 452 | } 453 | } 454 | 455 | // ----------------------------------------------------------------------------- 456 | static void sys_newfstatat(Process *proc, const Args &args) 457 | { 458 | const int dirfd = args[0]; 459 | const fs::path path = proc->Normalise(dirfd, ReadString(args.PID, args[1])); 460 | 461 | if (args.Return >= 0) { 462 | proc->AddTouched(path); 463 | } 464 | } 465 | 466 | // ----------------------------------------------------------------------------- 467 | static void sys_renameat(Process *proc, const Args &args) 468 | { 469 | const int odirfd = args[0]; 470 | const fs::path opath = proc->Normalise(odirfd, ReadString(args.PID, args[1])); 471 | const int ndirfd = args[2]; 472 | const fs::path npath = proc->Normalise(ndirfd, ReadString(args.PID, args[3])); 473 | 474 | if (args.Return >= 0) { 475 | proc->Rename(opath, npath); 476 | } 477 | } 478 | 479 | // ----------------------------------------------------------------------------- 480 | static void sys_unlinkat(Process *proc, const Args &args) 481 | { 482 | const int fd = args[0]; 483 | const fs::path path = proc->Normalise(fd, ReadString(args.PID, args[1])); 484 | 485 | if (args.Return >= 0) { 486 | proc->Remove(path); 487 | } 488 | } 489 | 490 | // ----------------------------------------------------------------------------- 491 | static void sys_readlinkat(Process *proc, const Args &args) 492 | { 493 | const int fd = args[0]; 494 | const fs::path path = proc->Normalise(fd, ReadString(args.PID, args[1])); 495 | if (args.Return >= 0) { 496 | proc->AddInput(path); 497 | } 498 | } 499 | 500 | // ----------------------------------------------------------------------------- 501 | static void sys_faccessat(Process *proc, const Args &args) 502 | { 503 | const int fd = args[0]; 504 | const fs::path path = proc->Normalise(fd, ReadString(args.PID, args[1])); 505 | 506 | if (args.Return >= 0) { 507 | proc->AddInput(path); 508 | } 509 | } 510 | 511 | // ----------------------------------------------------------------------------- 512 | static void sys_splice(Process *proc, const Args &args) 513 | { 514 | throw std::runtime_error("not implemented"); 515 | } 516 | 517 | // ----------------------------------------------------------------------------- 518 | static void sys_fallocate(Process *proc, const Args &args) 519 | { 520 | if (args.Return >= 0) { 521 | proc->AddOutput(args[0]); 522 | } 523 | } 524 | 525 | // ----------------------------------------------------------------------------- 526 | static void sys_eventfd2(Process *proc, const Args &args) 527 | { 528 | const int flags = args[1]; 529 | const int fd = args.Return; 530 | 531 | if (args.Return >= 0) { 532 | proc->MapFd(fd, "/proc/" + std::to_string(args.PID) + "/event"); 533 | proc->SetCloseExec(fd, flags & EFD_CLOEXEC); 534 | } 535 | } 536 | 537 | // ----------------------------------------------------------------------------- 538 | static void sys_dup3(Process *proc, const Args &args) 539 | { 540 | const int oldfd = args[0]; 541 | const int newfd = args[1]; 542 | const int flags = args[2]; 543 | 544 | if (args.Return >= 0) { 545 | proc->DupFd(oldfd, newfd); 546 | } 547 | 548 | proc->SetCloseExec(newfd, flags & O_CLOEXEC); 549 | } 550 | 551 | // ----------------------------------------------------------------------------- 552 | static void sys_pipe2(Process *proc, const Args &args) 553 | { 554 | int fds[2]; 555 | ReadBuffer(args.PID, fds, args[0], 2 * sizeof(int)); 556 | const int flags = args[1]; 557 | 558 | if (args.Return >= 0) { 559 | proc->Pipe(fds[0], fds[1]); 560 | 561 | const bool closeExec = flags & O_CLOEXEC; 562 | proc->SetCloseExec(fds[0], closeExec); 563 | proc->SetCloseExec(fds[1], closeExec); 564 | } 565 | } 566 | 567 | // ----------------------------------------------------------------------------- 568 | static void sys_ignore(Process *proc, const Args &args) 569 | { 570 | } 571 | 572 | typedef void (*HandlerFn) (Process *proc, const Args &args); 573 | 574 | static const HandlerFn kHandlers[] = 575 | { 576 | /* 0x000 */ [SYS_read ] = sys_read, 577 | /* 0x001 */ [SYS_write ] = sys_write, 578 | /* 0x002 */ [SYS_open ] = sys_open, 579 | /* 0x003 */ [SYS_close ] = sys_close, 580 | /* 0x004 */ [SYS_stat ] = sys_stat, 581 | /* 0x005 */ [SYS_fstat ] = sys_fstat, 582 | /* 0x006 */ [SYS_lstat ] = sys_lstat, 583 | /* 0x007 */ [SYS_poll ] = sys_ignore, 584 | /* 0x008 */ [SYS_lseek ] = sys_ignore, 585 | /* 0x009 */ [SYS_mmap ] = sys_mmap, 586 | /* 0x00A */ [SYS_mprotect ] = sys_ignore, 587 | /* 0x00B */ [SYS_munmap ] = sys_ignore, 588 | /* 0x00C */ [SYS_brk ] = sys_ignore, 589 | /* 0x00D */ [SYS_rt_sigaction ] = sys_ignore, 590 | /* 0x00E */ [SYS_rt_sigprocmask ] = sys_ignore, 591 | /* 0x00F */ [SYS_rt_sigreturn ] = sys_ignore, 592 | /* 0x010 */ [SYS_ioctl ] = sys_ignore, 593 | /* 0x011 */ [SYS_pread64 ] = sys_pread64, 594 | /* 0x013 */ [SYS_readv ] = sys_readv, 595 | /* 0x014 */ [SYS_writev ] = sys_writev, 596 | /* 0x015 */ [SYS_access ] = sys_access, 597 | /* 0x016 */ [SYS_pipe ] = sys_pipe, 598 | /* 0x017 */ [SYS_select ] = sys_ignore, 599 | /* 0x018 */ [SYS_sched_yield ] = sys_ignore, 600 | /* 0x019 */ [SYS_mremap ] = sys_ignore, 601 | /* 0x01a */ [SYS_msync ] = sys_ignore, 602 | /* 0x01b */ [SYS_mincore ] = sys_ignore, 603 | /* 0x01c */ [SYS_madvise ] = sys_ignore, 604 | /* 0x020 */ [SYS_dup ] = sys_dup, 605 | /* 0x021 */ [SYS_dup2 ] = sys_dup2, 606 | /* 0x023 */ [SYS_nanosleep ] = sys_ignore, 607 | /* 0x025 */ [SYS_alarm ] = sys_ignore, 608 | /* 0x026 */ [SYS_setitimer ] = sys_ignore, 609 | /* 0x027 */ [SYS_getpid ] = sys_ignore, 610 | /* 0x029 */ [SYS_socket ] = sys_socket, 611 | /* 0x02A */ [SYS_connect ] = sys_ignore, 612 | /* 0x02C */ [SYS_sendto ] = sys_ignore, 613 | /* 0x02D */ [SYS_recvfrom ] = sys_ignore, 614 | /* 0x02E */ [SYS_sendmsg ] = sys_ignore, 615 | /* 0x02F */ [SYS_recvmsg ] = sys_ignore, 616 | /* 0x031 */ [SYS_bind ] = sys_ignore, 617 | /* 0x033 */ [SYS_getsockname ] = sys_ignore, 618 | /* 0x034 */ [SYS_getpeername ] = sys_ignore, 619 | /* 0x035 */ [SYS_socketpair ] = sys_ignore, 620 | /* 0x036 */ [SYS_setsockopt ] = sys_ignore, 621 | /* 0x037 */ [SYS_getsockopt ] = sys_ignore, 622 | /* 0x038 */ [SYS_clone ] = sys_ignore, 623 | /* 0x039 */ [SYS_fork ] = sys_ignore, 624 | /* 0x03A */ [SYS_vfork ] = sys_ignore, 625 | /* 0x03B */ [SYS_execve ] = sys_ignore, 626 | /* 0x03D */ [SYS_wait4 ] = sys_ignore, 627 | /* 0x03F */ [SYS_uname ] = sys_ignore, 628 | /* 0x048 */ [SYS_fcntl ] = sys_fcntl, 629 | /* 0x049 */ [SYS_flock ] = sys_ignore, 630 | /* 0x04A */ [SYS_fsync ] = sys_ignore, 631 | /* 0x04D */ [SYS_ftruncate ] = sys_ftruncate, 632 | /* 0x04E */ [SYS_getdents ] = sys_getdents, 633 | /* 0x04F */ [SYS_getcwd ] = sys_ignore, 634 | /* 0x050 */ [SYS_chdir ] = sys_chdir, 635 | /* 0x051 */ [SYS_fchdir ] = sys_fchdir, 636 | /* 0x052 */ [SYS_rename ] = sys_rename, 637 | /* 0x053 */ [SYS_mkdir ] = sys_mkdir, 638 | /* 0x054 */ [SYS_rmdir ] = sys_rmdir, 639 | /* 0x055 */ [SYS_creat ] = sys_creat, 640 | /* 0x056 */ [SYS_link ] = sys_link, 641 | /* 0x057 */ [SYS_unlink ] = sys_unlink, 642 | /* 0x058 */ [SYS_symlink ] = sys_symlink, 643 | /* 0x059 */ [SYS_readlink ] = sys_readlink, 644 | /* 0x05A */ [SYS_chmod ] = sys_ignore, 645 | /* 0x05B */ [SYS_fchmod ] = sys_ignore, 646 | /* 0x05C */ [SYS_chown ] = sys_ignore, 647 | /* 0x05F */ [SYS_umask ] = sys_ignore, 648 | /* 0x060 */ [SYS_gettimeofday ] = sys_ignore, 649 | /* 0x061 */ [SYS_getrlimit ] = sys_ignore, 650 | /* 0x062 */ [SYS_getrusage ] = sys_ignore, 651 | /* 0x063 */ [SYS_sysinfo ] = sys_ignore, 652 | /* 0x064 */ [SYS_times ] = sys_ignore, 653 | /* 0x066 */ [SYS_getuid ] = sys_ignore, 654 | /* 0x068 */ [SYS_getgid ] = sys_ignore, 655 | /* 0x06B */ [SYS_geteuid ] = sys_ignore, 656 | /* 0x06C */ [SYS_getegid ] = sys_ignore, 657 | /* 0x06D */ [SYS_setpgid ] = sys_ignore, 658 | /* 0x06E */ [SYS_getppid ] = sys_ignore, 659 | /* 0x06F */ [SYS_getpgrp ] = sys_ignore, 660 | /* 0x070 */ [SYS_setsid ] = sys_ignore, 661 | /* 0x071 */ [SYS_setreuid ] = sys_ignore, 662 | /* 0x073 */ [SYS_getgroups ] = sys_ignore, 663 | /* 0x07F */ [SYS_rt_sigpending ] = sys_ignore, 664 | /* 0x083 */ [SYS_sigaltstack ] = sys_ignore, 665 | /* 0x084 */ [SYS_utime ] = sys_utime, 666 | /* 0x087 */ [SYS_personality ] = sys_ignore, 667 | /* 0x089 */ [SYS_statfs ] = sys_ignore, 668 | /* 0x08A */ [SYS_fstatfs ] = sys_ignore, 669 | /* 0x09D */ [SYS_prctl ] = sys_ignore, 670 | /* 0x09E */ [SYS_arch_prctl ] = sys_ignore, 671 | /* 0x0A0 */ [SYS_setrlimit ] = sys_ignore, 672 | /* 0x0A5 */ [SYS_linkat ] = sys_linkat, 673 | /* 0x0BA */ [SYS_gettid ] = sys_ignore, 674 | /* 0x0BE */ [SYS_fsetxattr ] = sys_fsetxattr, 675 | /* 0x0BF */ [SYS_getxattr ] = sys_getxattr, 676 | /* 0x0C0 */ [SYS_lgetxattr ] = sys_lgetxattr, 677 | /* 0x0C3 */ [SYS_llistxattr ] = sys_llistxattr, 678 | /* 0x0C4 */ [SYS_flistxattr ] = sys_flistxattr, 679 | /* 0x0C9 */ [SYS_time ] = sys_ignore, 680 | /* 0x0CA */ [SYS_futex ] = sys_ignore, 681 | /* 0x0CB */ [SYS_sched_setaffinity ] = sys_ignore, 682 | /* 0x0CC */ [SYS_sched_getaffinity ] = sys_ignore, 683 | /* 0x0D5 */ [SYS_epoll_create ] = sys_epoll_create, 684 | /* 0x0D9 */ [SYS_getdents64 ] = sys_getdents64, 685 | /* 0x0DA */ [SYS_set_tid_address ] = sys_ignore, 686 | /* 0x0DB */ [SYS_restart_syscall ] = sys_ignore, 687 | /* 0x0DE */ [SYS_timer_create ] = sys_ignore, 688 | /* 0x0DF */ [SYS_timer_settime ] = sys_ignore, 689 | /* 0x0E0 */ [SYS_timer_gettime ] = sys_ignore, 690 | /* 0x0E1 */ [SYS_timer_getoverrun ] = sys_ignore, 691 | /* 0x0E2 */ [SYS_timer_delete ] = sys_ignore, 692 | /* 0x0DD */ [SYS_fadvise64 ] = sys_ignore, 693 | /* 0x0E4 */ [SYS_clock_gettime ] = sys_ignore, 694 | /* 0x0E5 */ [SYS_clock_getres ] = sys_ignore, 695 | /* 0x0E7 */ [SYS_exit_group ] = sys_ignore, 696 | /* 0x0E8 */ [SYS_epoll_wait ] = sys_ignore, 697 | /* 0x0E9 */ [SYS_epoll_ctl ] = sys_ignore, 698 | /* 0x0EA */ [SYS_tgkill ] = sys_ignore, 699 | /* 0x0EB */ [SYS_utimes ] = sys_ignore, 700 | /* 0x0F7 */ [SYS_waitid ] = sys_ignore, 701 | /* 0x101 */ [SYS_openat ] = sys_openat, 702 | /* 0x102 */ [SYS_mkdirat ] = sys_mkdirat, 703 | /* 0x106 */ [SYS_newfstatat ] = sys_newfstatat, 704 | /* 0x107 */ [SYS_unlinkat ] = sys_unlinkat, 705 | /* 0x108 */ [SYS_renameat ] = sys_renameat, 706 | /* 0x10B */ [SYS_readlinkat ] = sys_readlinkat, 707 | /* 0x10C */ [SYS_fchmodat ] = sys_ignore, 708 | /* 0x10D */ [SYS_faccessat ] = sys_faccessat, 709 | /* 0x10E */ [SYS_pselect6 ] = sys_ignore, 710 | /* 0x10F */ [SYS_ppoll ] = sys_ignore, 711 | /* 0x111 */ [SYS_set_robust_list ] = sys_ignore, 712 | /* 0x113 */ [SYS_splice ] = sys_splice, 713 | /* 0x118 */ [SYS_utimensat ] = sys_ignore, 714 | /* 0x119 */ [SYS_epoll_pwait ] = sys_ignore, 715 | /* 0x11D */ [SYS_fallocate ] = sys_fallocate, 716 | /* 0x122 */ [SYS_eventfd2 ] = sys_eventfd2, 717 | /* 0x123 */ [SYS_epoll_create1 ] = sys_ignore, 718 | /* 0x124 */ [SYS_dup3 ] = sys_dup3, 719 | /* 0x125 */ [SYS_pipe2 ] = sys_pipe2, 720 | /* 0x12E */ [SYS_prlimit64 ] = sys_ignore, 721 | /* 0x133 */ [SYS_sendmmsg ] = sys_ignore, 722 | /* 0x13E */ [SYS_getrandom ] = sys_ignore, 723 | }; 724 | 725 | // ----------------------------------------------------------------------------- 726 | void Handle(Trace *trace, int64_t sno, const Args &args) 727 | { 728 | if (sno < 0) { 729 | return; 730 | } 731 | 732 | if (sno > sizeof(kHandlers) / sizeof(kHandlers[0]) || !kHandlers[sno]) { 733 | throw std::runtime_error( 734 | "Unknown syscall " + std::to_string(sno) + " in " + 735 | trace->GetFileName(trace->GetTrace(args.PID)->GetImage()) 736 | ); 737 | } 738 | 739 | auto *proc = trace->GetTrace(args.PID); 740 | 741 | try { 742 | kHandlers[sno](proc, args); 743 | } catch (std::exception &ex) { 744 | throw std::runtime_error( 745 | "Exception while handling syscall " + std::to_string(sno) + 746 | " in process " + std::to_string(proc->GetUID()) + " (" + 747 | trace->GetFileName(proc->GetImage()) + 748 | "): " + 749 | ex.what() 750 | ); 751 | } 752 | } 753 | -------------------------------------------------------------------------------- /mkcheck/syscall.h: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | class Trace; 12 | 13 | 14 | 15 | /** 16 | * Number of system call arguments. 17 | */ 18 | constexpr size_t kSyscallArgs = 6; 19 | 20 | 21 | /** 22 | * State passed to the system call. 23 | */ 24 | struct Args { 25 | /// Identifier of the process. 26 | int64_t PID; 27 | /// Return value. 28 | int64_t Return; 29 | /// List of arguments. 30 | uint64_t Arg[kSyscallArgs]; 31 | 32 | uint64_t operator[] (size_t idx) const 33 | { 34 | assert(idx <= kSyscallArgs); 35 | return Arg[idx]; 36 | } 37 | }; 38 | 39 | 40 | /** 41 | * Handles a system call. 42 | */ 43 | void Handle(Trace *trace, int64_t sno, const Args &args); 44 | -------------------------------------------------------------------------------- /mkcheck/trace.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #include 6 | #include 7 | 8 | #include "proc.h" 9 | #include "trace.h" 10 | 11 | 12 | 13 | // ----------------------------------------------------------------------------- 14 | Trace::Trace() 15 | : nextUID_(1) 16 | , nextFID_(1) 17 | { 18 | } 19 | 20 | // ----------------------------------------------------------------------------- 21 | Trace::~Trace() 22 | { 23 | } 24 | 25 | // ----------------------------------------------------------------------------- 26 | void Trace::Dump(const fs::path &output) 27 | { 28 | std::ofstream os(output.string()); 29 | 30 | // Save the list of files. 31 | os << "{" << std::endl; 32 | { 33 | os << "\"files\": [" << std::endl; 34 | for (auto it = fileInfos_.begin(); it != fileInfos_.end();) { 35 | const auto &info = it->second; 36 | os << "{"; 37 | os << "\"id\": " << it->first << ","; 38 | os << "\"name\": \"" << info.Name << "\""; 39 | if (info.Deleted) { 40 | os << ",\"deleted\": true"; 41 | } 42 | if (info.Exists) { 43 | os << ",\"exists\": true"; 44 | } 45 | if (!info.Deps.empty()) { 46 | os << ",\"deps\": ["; 47 | for (auto jt = info.Deps.begin(); jt != info.Deps.end();) { 48 | os << *jt; 49 | if (++jt != info.Deps.end()) { 50 | os << ","; 51 | } 52 | } 53 | os << "]"; 54 | } 55 | os << "}"; 56 | if (++it != fileInfos_.end()) { 57 | os << "," << std::endl; 58 | } 59 | } 60 | os << "]," << std::endl; 61 | } 62 | 63 | // Save the list of processes. 64 | { 65 | os << "\"procs\": [" << std::endl; 66 | for (auto it = procs_.begin(); it != procs_.end();) { 67 | it->second->Dump(os); 68 | if (++it != procs_.end()) { 69 | os << ","; 70 | } 71 | } 72 | os << "]" << std::endl; 73 | } 74 | os << "}" << std::endl; 75 | } 76 | 77 | // ----------------------------------------------------------------------------- 78 | void Trace::ShareTrace(pid_t parent, pid_t pid) 79 | { 80 | auto it = procs_.find(parent); 81 | if (it == procs_.end()) { 82 | throw std::runtime_error("Process " + std::to_string(parent) + " missing"); 83 | } 84 | procs_.emplace(pid, it->second); 85 | } 86 | 87 | // ----------------------------------------------------------------------------- 88 | void Trace::SpawnTrace(pid_t parent, pid_t pid) 89 | { 90 | // Find the working directory. 91 | fs::path cwd; 92 | uint64_t parentUID; 93 | uint64_t image; 94 | FDSet fdSet; 95 | { 96 | auto it = procs_.find(parent); 97 | if (it == procs_.end()) { 98 | char buffer[PATH_MAX]; 99 | getcwd(buffer, PATH_MAX); 100 | cwd = buffer; 101 | image = 0; 102 | parentUID = 0; 103 | 104 | fdSet.emplace_back(0, "/dev/stdin", false); 105 | fdSet.emplace_back(1, "/dev/stdout", false); 106 | fdSet.emplace_back(2, "/dev/stderr", false); 107 | } else { 108 | auto proc = it->second; 109 | cwd = proc->GetCwd(); 110 | image = proc->GetImage(); 111 | parentUID = proc->GetUID(); 112 | fdSet = proc->GetAllFDs(); 113 | } 114 | } 115 | 116 | // Create the COW trace. 117 | procs_.emplace(pid, std::make_shared( 118 | this, 119 | pid, 120 | parentUID, 121 | nextUID_++, 122 | image, 123 | fdSet, 124 | cwd, 125 | true 126 | )); 127 | } 128 | 129 | // ----------------------------------------------------------------------------- 130 | void Trace::StartTrace(pid_t pid, const fs::path &image) 131 | { 132 | // Find the previous copy - it must exist. 133 | auto it = procs_.find(pid); 134 | assert(it != procs_.end()); 135 | auto proc = it->second; 136 | 137 | // Replace with a non-COW trace which has a new image. 138 | it->second = std::make_shared( 139 | this, 140 | pid, 141 | proc->GetParent(), 142 | nextUID_++, 143 | Find(proc->Normalise(image)), 144 | proc->GetInheritedFDs(), 145 | proc->GetCwd(), 146 | false 147 | ); 148 | } 149 | 150 | // ----------------------------------------------------------------------------- 151 | void Trace::EndTrace(pid_t pid) 152 | { 153 | } 154 | 155 | // ----------------------------------------------------------------------------- 156 | Process *Trace::GetTrace(pid_t pid) 157 | { 158 | auto it = procs_.find(pid); 159 | assert(it != procs_.end()); 160 | return it->second.get(); 161 | } 162 | 163 | // ----------------------------------------------------------------------------- 164 | void Trace::Unlink(const fs::path &path) 165 | { 166 | auto fileID = Find(path); 167 | auto &info = fileInfos_.find(fileID)->second; 168 | info.Deleted = true; 169 | info.Exists = false; 170 | } 171 | 172 | // ----------------------------------------------------------------------------- 173 | uint64_t Trace::Find(const fs::path &path) 174 | { 175 | if (!path.is_absolute()) { 176 | throw std::runtime_error("Path not absolute: " + path.string()); 177 | } 178 | 179 | const std::string name = path.string(); 180 | auto it = fileIDs_.find(name); 181 | if (it == fileIDs_.end()) { 182 | uint64_t id = nextFID_++; 183 | fileIDs_.emplace(name, id); 184 | fileInfos_.emplace(id, FileInfo(name, fs::exists(path))); 185 | return id; 186 | } else { 187 | return it->second; 188 | } 189 | } 190 | 191 | // ----------------------------------------------------------------------------- 192 | void Trace::Create(const fs::path &path) 193 | { 194 | const std::string name = path.string(); 195 | auto it = fileIDs_.find(name); 196 | if (it == fileIDs_.end()) { 197 | throw std::runtime_error("Unknown file: " + path.string()); 198 | } 199 | 200 | FileInfo &info = fileInfos_.find(it->second)->second; 201 | info.Deleted = false; 202 | info.Exists = true; 203 | } 204 | 205 | // ----------------------------------------------------------------------------- 206 | std::string Trace::GetFileName(uint64_t fid) const 207 | { 208 | auto it = fileInfos_.find(fid); 209 | assert(it != fileInfos_.end()); 210 | return it->second.Name; 211 | } 212 | 213 | // ----------------------------------------------------------------------------- 214 | void Trace::AddDependency(const fs::path &src, const fs::path &dst) 215 | { 216 | const auto sID = Find(src); 217 | const auto dID = Find(dst); 218 | auto &info = fileInfos_.find(dID)->second; 219 | info.Deps.insert(sID); 220 | } 221 | -------------------------------------------------------------------------------- /mkcheck/trace.h: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "fd.h" 14 | 15 | class Process; 16 | 17 | 18 | 19 | /** 20 | * The trace we are constructing. 21 | */ 22 | class Trace final { 23 | public: 24 | /** 25 | * Initiates a new trace, storing output in the specified directory. 26 | */ 27 | Trace(); 28 | 29 | /** 30 | * Cleanup. 31 | */ 32 | ~Trace(); 33 | 34 | /// Dumps the trace to a file. 35 | void Dump(const fs::path &output); 36 | 37 | /// Shares a state between parent and child. 38 | void ShareTrace(pid_t parent, pid_t pid); 39 | /// Spawns a new process. 40 | void SpawnTrace(pid_t parent, pid_t pid); 41 | /// Starts a new trace. 42 | void StartTrace(pid_t pid, const fs::path &image); 43 | /// Closes trace. 44 | void EndTrace(pid_t pid); 45 | /// Returns the process for a PID. 46 | Process *GetTrace(pid_t pid); 47 | /// Unlinks a file. 48 | void Unlink(const fs::path &path); 49 | /// Finds a file. 50 | uint64_t Find(const fs::path &path); 51 | /// Clears delete/exists flags when a syscall creates a file. 52 | void Create(const fs::path &target); 53 | /// Returns the name of a file. 54 | std::string GetFileName(uint64_t fid) const; 55 | /// Adds a symlink/rename dependency between two files. 56 | void AddDependency(const fs::path &src, const fs::path &dst); 57 | 58 | private: 59 | /// Next available UID. 60 | uint64_t nextUID_; 61 | /// Next available file ID. 62 | uint64_t nextFID_; 63 | /// Map of processes. 64 | std::unordered_map> procs_; 65 | /// Map of files to current IDs. 66 | std::unordered_map fileIDs_; 67 | 68 | /// Information about a file. 69 | struct FileInfo { 70 | /// Name of the file. 71 | std::string Name; 72 | /// Flag indicating if this one exists by the end of the build. 73 | bool Deleted; 74 | /// Flag indicating if the file exists or not. 75 | bool Exists; 76 | /// List of other files this depends on. 77 | std::set Deps; 78 | 79 | /// Constructs a new info object. 80 | FileInfo(const std::string &Name, bool exists) 81 | : Name(Name) 82 | , Deleted(false) 83 | , Exists(exists) 84 | { 85 | } 86 | }; 87 | 88 | /// Set of names attached to an ID. 89 | std::unordered_map fileInfos_; 90 | }; 91 | -------------------------------------------------------------------------------- /mkcheck/util.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #include "util.h" 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | 13 | // ----------------------------------------------------------------------------- 14 | constexpr size_t kPageSize = 4096; 15 | 16 | // ----------------------------------------------------------------------------- 17 | ssize_t ReadBuffer(pid_t pid, void *dst, uint64_t src, size_t len) 18 | { 19 | const struct iovec local = 20 | { 21 | .iov_base = dst, 22 | .iov_len = len 23 | }; 24 | 25 | const struct iovec remote = 26 | { 27 | .iov_base = reinterpret_cast(src), 28 | .iov_len = len 29 | }; 30 | 31 | return process_vm_readv(pid, &local, 1, &remote, 1, 0); 32 | } 33 | 34 | // ----------------------------------------------------------------------------- 35 | std::string ReadString(pid_t pid, uint64_t addr) 36 | { 37 | return ReadString(pid, addr, std::numeric_limits::max()); 38 | } 39 | 40 | // ----------------------------------------------------------------------------- 41 | std::string ReadString(pid_t pid, uint64_t addr, size_t len) 42 | { 43 | std::string result; 44 | char buffer[kPageSize]; 45 | uint64_t read = 0; 46 | 47 | for (size_t i = 0; i < len; ++i) { 48 | const uint64_t end = (addr + kPageSize) & (kPageSize - 1); 49 | const uint64_t len = kPageSize - end; 50 | 51 | ssize_t count = ReadBuffer(pid, buffer, addr, len); 52 | if (count < 0) { 53 | throw std::runtime_error( 54 | "Cannot read from child memory (errno = " + 55 | std::to_string(errno) + 56 | ")" 57 | ); 58 | } 59 | 60 | for (size_t i = 0; i < count; ++i) { 61 | if (buffer[i] == '\0') { 62 | result.append(buffer, i); 63 | return result; 64 | } 65 | } 66 | 67 | result.append(buffer, count); 68 | addr += count; 69 | } 70 | 71 | return result; 72 | } 73 | -------------------------------------------------------------------------------- /mkcheck/util.h: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | 11 | 12 | /** 13 | * Reads a buffer from a child's address space. 14 | */ 15 | ssize_t ReadBuffer(pid_t pid, void *dst, uint64_t src, size_t len); 16 | 17 | /** 18 | * Reads a string from the child's address space. 19 | */ 20 | std::string ReadString(pid_t pid, uint64_t addr); 21 | 22 | /** 23 | * Reads a string, up to a given length. 24 | */ 25 | std::string ReadString(pid_t pid, uint64_t addr, size_t len); 26 | -------------------------------------------------------------------------------- /test/make/Makefile: -------------------------------------------------------------------------------- 1 | # This file is part of the mkcheck project. 2 | # Licensing information can be found in the LICENSE file. 3 | # (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | CC = gcc 6 | OUT = out 7 | 8 | .PHONY: dirs clean 9 | 10 | all : dirs $(OUT)/main 11 | 12 | dirs : $(OUT) 13 | 14 | $(OUT) : 15 | mkdir -p $(OUT) 16 | 17 | $(OUT)/lib_a.o : lib_a/lib_a.c lib_a/lib_a.h 18 | $(CC) -c -o $(OUT)/lib_a.o lib_a/lib_a.c 19 | 20 | $(OUT)/lib_b.o : lib_b/lib_b.c lib_b/lib_b.h 21 | $(CC) -c -o $(OUT)/lib_b.o lib_b/lib_b.c 22 | 23 | # missing deps on lib_b/lib_b.h 24 | $(OUT)/a.o : a.c a.h 25 | $(CC) -c -o $(OUT)/a.o a.c 26 | 27 | # extra deps on c.h 28 | $(OUT)/b.o : b.c b.h a.h c.h 29 | $(CC) -c -o $(OUT)/b.o b.c 30 | 31 | # missing deps on a.h, d.h 32 | $(OUT)/c.o : c.c c.h 33 | $(CC) -c -o $(OUT)/c.o c.c 34 | 35 | # missing deps on a.h, b.h, c.h, lib_a/lib_a.h, lib_b/lib_b.h 36 | $(OUT)/main.o : main.c 37 | $(CC) -c -o $(OUT)/main.o main.c 38 | 39 | $(OUT)/main : $(OUT)/main.o $(OUT)/a.o $(OUT)/b.o $(OUT)/c.o $(OUT)/lib_a.o $(OUT)/lib_b.o 40 | $(CC) -o $(OUT)/main $(OUT)/main.o $(OUT)/a.o $(OUT)/b.o $(OUT)/c.o $(OUT)/lib_a.o $(OUT)/lib_b.o 41 | 42 | clean : 43 | rm -rf $(OUT) 44 | 45 | test : 46 | make clean 47 | ../../build/mkcheck -o /tmp/graph -- make -j1 48 | @echo "Fuzzing parallel" 49 | python3 ../../tools/fuzz_test --graph-path=/tmp/graph fuzz 50 | -------------------------------------------------------------------------------- /test/make/a.c: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #include 6 | #include "lib_b/lib_b.h" 7 | #include "a.h" 8 | 9 | void a() 10 | { 11 | lib_b(); 12 | puts("A"); 13 | } 14 | -------------------------------------------------------------------------------- /test/make/a.h: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #pragma once 6 | 7 | void a(); 8 | -------------------------------------------------------------------------------- /test/make/b.c: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #include 6 | #include "b.h" 7 | #include "a.h" 8 | 9 | void b() 10 | { 11 | a(); 12 | puts("B"); 13 | } 14 | -------------------------------------------------------------------------------- /test/make/b.h: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #pragma once 6 | 7 | void b(); 8 | -------------------------------------------------------------------------------- /test/make/c.c: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #include 6 | #include "c.h" 7 | #include "a.h" 8 | #include "d.h" 9 | 10 | void c() 11 | { 12 | a(); 13 | puts("C"); 14 | } 15 | -------------------------------------------------------------------------------- /test/make/c.h: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #pragma once 6 | 7 | void c(); 8 | -------------------------------------------------------------------------------- /test/make/d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/make/lib_a/lib_a.c: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #include 6 | #include "lib_a.h" 7 | 8 | void lib_a() 9 | { 10 | puts("LIB_A"); 11 | } 12 | -------------------------------------------------------------------------------- /test/make/lib_a/lib_a.h: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #pragma once 6 | 7 | void lib_a(); 8 | -------------------------------------------------------------------------------- /test/make/lib_b/lib_b.c: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #include 6 | #include "lib_b.h" 7 | 8 | void lib_b() 9 | { 10 | puts("LIB_B"); 11 | } 12 | -------------------------------------------------------------------------------- /test/make/lib_b/lib_b.h: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #pragma once 6 | 7 | void lib_b(); 8 | -------------------------------------------------------------------------------- /test/make/main.c: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | #include 6 | #include "lib_a/lib_a.h" 7 | #include "lib_b/lib_b.h" 8 | #include "a.h" 9 | #include "b.h" 10 | #include "c.h" 11 | 12 | int main() 13 | { 14 | lib_a(); 15 | lib_b(); 16 | a(); 17 | b(); 18 | c(); 19 | return EXIT_FAILURE; 20 | } 21 | -------------------------------------------------------------------------------- /test/parallel/Makefile: -------------------------------------------------------------------------------- 1 | # This file is part of the mkcheck project. 2 | # Licensing information can be found in the LICENSE file. 3 | # (C) 2018 Nandor Licker. All rights reserved. 4 | 5 | CC = gcc 6 | OUT = out 7 | 8 | 9 | .PHONY: dirs clean 10 | 11 | 12 | all : dirs $(OUT)/main 13 | 14 | dirs : $(OUT) 15 | 16 | $(OUT) : 17 | mkdir -p $(OUT) 18 | 19 | 20 | $(OUT)/a.h : a.in 21 | head -n 4 a.in > $(OUT)/a.h 22 | tail -n 7 a.in > $(OUT)/a.cpp 23 | 24 | $(OUT)/a.o : $(OUT)/a.h $(OUT)/a.cpp 25 | $(CC) -c -o $(OUT)/a.o $(OUT)/a.cpp 26 | 27 | $(OUT)/b.h : b.in 28 | head -n 4 b.in > $(OUT)/b.h 29 | tail -n 7 b.in > $(OUT)/b.cpp 30 | 31 | $(OUT)/b.o : $(OUT)/b.h $(OUT)/b.cpp 32 | $(CC) -c -o $(OUT)/b.o $(OUT)/b.cpp 33 | 34 | $(OUT)/c.h : c.in 35 | head -n 4 c.in > $(OUT)/c.h 36 | tail -n 11 c.in > $(OUT)/c.cpp 37 | 38 | # missing deps on a.h, b.h 39 | $(OUT)/c.o : $(OUT)/c.h $(OUT)/c.cpp 40 | $(CC) -c -o $(OUT)/c.o $(OUT)/c.cpp 41 | 42 | 43 | $(OUT)/main.o : main.cpp $(OUT)/a.h $(OUT)/b.h $(OUT)/c.h 44 | $(CC) -c -o $(OUT)/main.o main.cpp 45 | 46 | $(OUT)/main : $(OUT)/a.o $(OUT)/b.o $(OUT)/c.o $(OUT)/main.o 47 | $(CC) -o $(OUT)/main $^ 48 | 49 | clean : 50 | rm -rf $(OUT) 51 | 52 | test : 53 | make clean 54 | ../../build/mkcheck -o /tmp/graph -- make -j1 55 | @echo "Fuzzing parallel" 56 | python3 ../../tools/fuzz_test --graph-path=/tmp/graph fuzz 57 | @echo "Race testing parallel" 58 | python3 ../../tools/fuzz_test --graph-path=/tmp/graph race 59 | 60 | -------------------------------------------------------------------------------- /test/parallel/a.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void a(); 4 | 5 | -- 6 | #include 7 | 8 | void a() 9 | { 10 | printf("a\n"); 11 | } 12 | 13 | -------------------------------------------------------------------------------- /test/parallel/b.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void b(); 4 | 5 | -- 6 | #include 7 | 8 | void b() 9 | { 10 | printf("b\n"); 11 | } 12 | 13 | -------------------------------------------------------------------------------- /test/parallel/c.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void c(); 4 | 5 | -- 6 | #include 7 | #include "a.h" 8 | #include "b.h" 9 | 10 | void c() 11 | { 12 | a(); 13 | b(); 14 | printf("c\n"); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /test/parallel/main.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of the mkcheck project. 2 | // Licensing information can be found in the LICENSE file. 3 | // (C) 2018 Nandor Licker. All rights reserved. 4 | 5 | #include 6 | 7 | #include "out/a.h" 8 | #include "out/b.h" 9 | #include "out/c.h" 10 | 11 | 12 | 13 | int main() 14 | { 15 | a(); 16 | b(); 17 | c(); 18 | return EXIT_SUCCESS; 19 | } 20 | -------------------------------------------------------------------------------- /test/parallel/out/a.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void a() 4 | { 5 | printf("a\n"); 6 | } 7 | 8 | -------------------------------------------------------------------------------- /test/parallel/out/a.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void a(); 4 | 5 | -------------------------------------------------------------------------------- /test/parallel/out/b.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void b() 4 | { 5 | printf("b\n"); 6 | } 7 | 8 | -------------------------------------------------------------------------------- /test/parallel/out/b.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void b(); 4 | 5 | -------------------------------------------------------------------------------- /test/parallel/out/c.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "a.h" 3 | #include "b.h" 4 | 5 | void c() 6 | { 7 | a(); 8 | b(); 9 | printf("c\n"); 10 | } 11 | 12 | -------------------------------------------------------------------------------- /test/parallel/out/c.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void c(); 4 | 5 | -------------------------------------------------------------------------------- /test/parallel/out/main: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nandor/mkcheck/ea826e3092191b2b715b504172473ef50af59adf/test/parallel/out/main -------------------------------------------------------------------------------- /tools/fuzz_test/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | 5 | import argparse 6 | import os 7 | import yaml 8 | import re 9 | import resource 10 | import subprocess 11 | import stat 12 | import sys 13 | import tempfile 14 | import time 15 | 16 | from collections import defaultdict 17 | from graph import parse_graph 18 | from proc import run_proc 19 | from mtime import read_mtimes 20 | 21 | 22 | 23 | SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) 24 | PROJECT_PATH = os.path.abspath(os.path.join(SCRIPT_PATH, os.pardir, os.pardir)) 25 | TOOL_PATH = os.path.join(PROJECT_PATH, 'build', 'mkcheck') 26 | 27 | DELAY=1 28 | 29 | 30 | 31 | class HashTouchContext(object): 32 | """Context touching a file by adding a dummy extension.""" 33 | 34 | TEXT_EXT = [ 35 | '.asm', '.asm-generic', '.awk', '.S', '.bc', '.ld', 36 | '.conf', '.l', 'VERSION', 'imgdesc', '.py', '.txt', 37 | '.config', '.s', '.h' 38 | ] 39 | 40 | def __init__(self, path): 41 | self.path = path 42 | self.tmp = tempfile.TemporaryFile() 43 | 44 | def __enter__(self): 45 | time.sleep(DELAY) 46 | with open(self.path, 'rb') as f: 47 | data = f.read() 48 | is_text = data and data[0] == '#' 49 | self.tmp.write(data) 50 | 51 | with open(self.path, 'ab') as f: 52 | if not is_text: 53 | for ext in self.TEXT_EXT: 54 | if self.path.endswith(ext): 55 | is_text = True 56 | break 57 | f.write('\n' if is_text else '\0') 58 | time.sleep(DELAY) 59 | 60 | def __exit__(self, type, value, tb): 61 | self.tmp.seek(0) 62 | with open(self.path, 'wb') as f: 63 | f.write(self.tmp.read()) 64 | self.tmp.close() 65 | 66 | 67 | class TimeTouchContext(object): 68 | def __init__(self, path): 69 | self.path = path 70 | 71 | def __enter__(self): 72 | time.sleep(DELAY) 73 | if os.path.exists(self.path): os.utime(self.path, None) 74 | time.sleep(DELAY) 75 | 76 | def __exit__(self, type, value, tb): 77 | pass 78 | 79 | 80 | 81 | class Project(object): 82 | """Generic project: automake, cmake, make etc.""" 83 | 84 | # Based on past information, quite a few files are very 85 | # likely to be correct, thus they are excluded from 86 | # fuzzing in order to save on execution time. 87 | FILTER_IN = [] 88 | FILTER_TMP = [] 89 | FILTER_OUT = [] 90 | 91 | def __init__(self, rule_file, use_hash, args): 92 | self._use_hash = use_hash 93 | self._args = args 94 | 95 | if rule_file: 96 | with open(rule_file, 'r') as f: 97 | data = yaml.load(f.read()) 98 | filter_in = data.get('filter_in', []) 99 | filter_tmp = data.get('filter_tmp', []) 100 | filter_out = data.get('filter_out', []) 101 | else: 102 | filter_in = self.FILTER_IN 103 | filter_tmp = self.FILTER_TMP 104 | filter_out = self.FILTER_OUT 105 | 106 | def regex(pat): 107 | return re.compile(('^' + pat + '$').replace('\\', '\\\\')) 108 | 109 | self._filter_in = [regex(f) for f in filter_in] 110 | self._filter_tmp = [regex(f) for f in filter_tmp] 111 | self._filter_out = [regex(f) for f in filter_out] 112 | 113 | def filter_in(self, f): 114 | """Decides if the file is relevant to the project.""" 115 | 116 | if not os.access(f, os.W_OK): 117 | return False 118 | if f == TOOL_PATH: 119 | return False 120 | name = os.path.basename(f) 121 | if name.startswith('.'): 122 | return False 123 | 124 | for pattern in self._filter_in: 125 | if pattern.match(f): 126 | return False 127 | 128 | return True 129 | 130 | def filter_tmp(self, f): 131 | """Filters out uninteresting temporaries.""" 132 | 133 | if f == TOOL_PATH: 134 | return False 135 | 136 | name = os.path.basename(f) 137 | if name.startswith('.'): 138 | return False 139 | 140 | for pattern in self._filter_tmp: 141 | if pattern.match(f): 142 | return False 143 | 144 | return True 145 | 146 | def is_output(self, f): 147 | """Decides if a file should be considered an output.""" 148 | 149 | name = os.path.basename(f) 150 | if name.startswith('.'): 151 | return False 152 | 153 | for pattern in self._filter_out: 154 | if pattern.match(f): 155 | return False 156 | 157 | return True 158 | 159 | def touch(self, path): 160 | """Adjusts the content hash/timestamp of a file.""" 161 | if self._use_hash: 162 | return HashTouchContext(path) 163 | else: 164 | return TimeTouchContext(path) 165 | 166 | 167 | class Make(Project): 168 | 169 | FILTER_IN = [ 170 | '.*\.pyc', 171 | '.*\Makefile', 172 | '.*\.d', 173 | ] 174 | FILTER_TMP = [ 175 | '.*\.d', 176 | ] 177 | FILTER_OUT = [ 178 | '.*\.d', 179 | ] 180 | 181 | def __init__(self, root, graph, rule_file, use_hash, args): 182 | super(Make, self).__init__(rule_file, use_hash, args) 183 | self.projectPath = root 184 | self.buildPath = root 185 | self.graph = graph 186 | 187 | with open(os.devnull, 'w') as devnull: 188 | code = subprocess.Popen( 189 | ['make', '--dry-run', 'clean'], 190 | stdout=devnull, 191 | stderr=devnull, 192 | cwd=root 193 | ).wait() 194 | self.has_clean = code == 0 195 | 196 | def clean_build(self): 197 | """Performs a clean build of the project.""" 198 | 199 | # Run the build with mkcheck. 200 | run_proc( 201 | [ TOOL_PATH, "--output={0}".format(self.graph), "--", "make" ] + self._args, 202 | cwd=self.buildPath 203 | ) 204 | 205 | def clean(self): 206 | """Cleans the project.""" 207 | 208 | if self.has_clean: 209 | run_proc([ "make", "clean" ], cwd=self.buildPath) 210 | else: 211 | run_proc([ "git", "clean", "-fdx" ], cwd=self.buildPath) 212 | 213 | def build(self): 214 | """Performs an incremental build.""" 215 | 216 | run_proc([ "make"] + self._args, cwd=self.buildPath) 217 | 218 | def in_project(self, f): 219 | """Checks if a file is in the project.""" 220 | 221 | return f.startswith(self.projectPath) 222 | 223 | 224 | class SCons(Project): 225 | 226 | FILTER_IN = [ 227 | '.*\.c', 228 | '.*\.cc', 229 | '.*\.cpp', 230 | '.*\.h', 231 | '.*\.hpp', 232 | '.*\.i', 233 | '.*\.ipp', 234 | '.*\.o', 235 | '.*\.pyc', 236 | '.*\.sconf_temp', 237 | '.*/SConscript', 238 | '.*/SConstruct', 239 | '.*scons.*', 240 | ] 241 | FILTER_TMP = [ 242 | '.*\.o', 243 | '.*\.dblite', 244 | '.*\.a' 245 | ] 246 | FILTER_OUT = [ 247 | '.*\.internal', 248 | '.*\.includecache' 249 | ] 250 | 251 | def __init__(self, root, graph, rule_file, use_hash, args): 252 | super(SCons, self).__init__(rule_file, use_hash, args) 253 | self.projectPath = root 254 | self.buildPath = root 255 | self.graph = graph 256 | 257 | def clean_build(self): 258 | """Performs a clean build of the project.""" 259 | 260 | # Build once - needed for some projects. 261 | run_proc(['scons']) 262 | 263 | # Clean the project. 264 | self.clean() 265 | 266 | # Run the build with mkcheck. 267 | run_proc( 268 | [ TOOL_PATH, "--output={0}".format(self.graph), "--", "scons" ], 269 | cwd=self.buildPath 270 | ) 271 | 272 | def clean(self): 273 | """Cleans the project.""" 274 | 275 | run_proc([ "scons", "--clean" ], cwd=self.buildPath) 276 | 277 | def build(self): 278 | """Performs an incremental build.""" 279 | 280 | run_proc([ "scons", "-Q" ], cwd=self.buildPath) 281 | 282 | def filter_in(self, f): 283 | """Decides if the file is relevant to the project.""" 284 | 285 | if not super(SCons, self).filter_in(f): 286 | return False 287 | if not f.startswith(self.projectPath): 288 | return False 289 | return True 290 | 291 | def in_project(self, f): 292 | """Checks if a file is in the project.""" 293 | 294 | return f.startswith(self.projectPath) 295 | 296 | 297 | class CMakeProject(Project): 298 | """Project relying on CMake.""" 299 | 300 | FILTER_IN = [ 301 | '.*\.cpp', 302 | '.*\.cmake', 303 | '.*\.cmake.in', 304 | '.*\.c', 305 | '.*\.h', 306 | '.*\.hpp', 307 | '.*\.cc', 308 | '.*\.C', 309 | '.*\.make', 310 | '.*\.mk', 311 | '.*\.marks', 312 | '.*\.includecache', 313 | '.*\.check_cache', 314 | '.*\.pyc', 315 | '.*/Doxyfile\.in', 316 | '.*/CMakeLists.txt', 317 | '.*/flgas.make', 318 | '.*/depend.internal', 319 | '.*/link.txt', 320 | '.*/Makefile2', 321 | '.*/Makefile', 322 | '.*/CMakeCache.txt', 323 | '.*/feature_tests.cxx', 324 | '.*/.ninja_deps', 325 | '.*/.ninja_log', 326 | ] 327 | 328 | FILTER_TMP = [ 329 | '.*\.output', 330 | '.*\.includecache', 331 | '.*\.internal', 332 | '.*\.make', 333 | '.*\.a', 334 | '.*\.o', 335 | '.*\.so', 336 | ] 337 | 338 | FILTER_OUT = [ 339 | '.*\.internal', 340 | '.*\.includecache', 341 | '.*\.make', 342 | '.*swig.*', 343 | '.*doxygen.*', 344 | '.*INFO.*', 345 | ] 346 | 347 | def __init__(self, projectPath, buildPath, graph, rule_file, use_hash, args): 348 | super(CMakeProject, self).__init__(rule_file, use_hash, args) 349 | self.projectPath = projectPath 350 | self.graph = graph 351 | self.buildPath = buildPath 352 | 353 | if not os.path.isdir(self.buildPath): 354 | raise RuntimeError('Missing build directory') 355 | 356 | def clean_build(self): 357 | """Performs a clean build of the project.""" 358 | 359 | # Build once - needed for some projects. 360 | run_proc(self.BUILD, cwd=self.buildPath) 361 | 362 | # Clean the project. 363 | self.clean() 364 | 365 | # Run the build with mkcheck. 366 | run_proc( 367 | [ TOOL_PATH, "--output={0}".format(self.graph), "--" ] + self.BUILD, 368 | cwd=self.buildPath 369 | ) 370 | 371 | def clean(self): 372 | """Cleans the project.""" 373 | 374 | run_proc(self.CLEAN, cwd=self.buildPath) 375 | 376 | def build(self): 377 | """Performs an incremental build.""" 378 | 379 | run_proc(self.BUILD, cwd=self.buildPath) 380 | 381 | def filter_in(self, f): 382 | """Decides if the file is relevant to the project.""" 383 | 384 | if not super(CMakeProject, self).filter_in(f): 385 | return False 386 | if self.buildPath != self.projectPath and f.startswith(self.buildPath): 387 | return False 388 | if not f.startswith(self.projectPath): 389 | return False 390 | return True 391 | 392 | def filter_tmp(self, f): 393 | """Decides if an internal file is relevant for race detection.""" 394 | 395 | if not super(CMakeProject, self).filter_tmp(f): 396 | return False 397 | if not f.startswith(self.projectPath): 398 | return False 399 | return True 400 | 401 | def in_project(self, f): 402 | """Checks if a file is in the project.""" 403 | 404 | return f.startswith(self.projectPath) or f.startswith(self.buildPath) 405 | 406 | 407 | class CMakeMake(CMakeProject): 408 | """CMake project built using make.""" 409 | 410 | BUILD = [ 'make', '-j1' ] 411 | CLEAN = [ 'make', 'clean' ] 412 | 413 | class CMakeNinja(CMakeProject): 414 | """CMake project built using ninja.""" 415 | 416 | BUILD = [ 'ninja', '-j1' ] 417 | CLEAN = [ 'ninja', 'clean' ] 418 | 419 | 420 | def build_tool(): 421 | """Builds mkcheck.""" 422 | 423 | if os.path.isfile(os.path.join(PROJECT_PATH, 'build', 'build.ninja')): 424 | run_proc([ 'ninja' ], cwd=os.path.join(PROJECT_PATH, 'build')) 425 | return 426 | 427 | if os.path.isfile(os.path.join(PROJECT_PATH, 'build', 'Makefile')): 428 | run_proc([ 'make' ], cwd=os.path.join(PROJECT_PATH, 'build')) 429 | return 430 | 431 | raise RuntimeError('Cannot rebuild mkcheck') 432 | 433 | 434 | def reset_project(outputs): 435 | """Set the timestamp of all files in a project to be the same.""" 436 | 437 | stamp = time.time() 438 | for f in outputs: 439 | if os.path.exists(f): 440 | os.utime(f, (stamp, stamp)) 441 | 442 | def fuzz_test(project, files): 443 | """Find the set of inputs and outputs, as well as the graph.""" 444 | 445 | project.clean() 446 | project.build() 447 | 448 | inputs, outputs, built_by, graph = parse_graph(project.graph) 449 | 450 | if len(files) == 0: 451 | fuzzed = sorted([f for f in inputs - outputs if project.filter_in(f)]) 452 | else: 453 | fuzzed = [os.path.abspath(f) for f in files] 454 | 455 | count = len(fuzzed) 456 | for idx, input in zip(range(count), fuzzed): 457 | print('[{0}/{1}] {2}:'.format(idx + 1, count, input)) 458 | 459 | # Touch the file, run the incremental build and read timestamps. 460 | t0 = read_mtimes(outputs) 461 | with project.touch(input): project.build() 462 | t1 = read_mtimes(outputs) 463 | 464 | # Find the set of changed files. 465 | modified = set() 466 | for k, v in t0.items(): 467 | if v < t1[k] and project.is_output(k): 468 | modified.add(k) 469 | 470 | # Reset the project. 471 | reset_project(outputs) 472 | 473 | # Find expected changes. 474 | deps = graph.find_deps(input) 475 | expected = {f for f in deps & outputs if project.is_output(f)} 476 | 477 | # Report differences. 478 | if modified != expected: 479 | redundant = graph.prune_transitive(modified - expected) 480 | for f in sorted(redundant): 481 | print(' + {} ({})'.format(f, built_by[f])) 482 | 483 | missing = graph.prune_transitive(expected - modified) 484 | for f in sorted(missing): 485 | print(' - {} ({})'.format(f, built_by[f])) 486 | 487 | 488 | 489 | def query(project, files): 490 | """Queries the dependencies of a set of files.""" 491 | 492 | _, _, built_by, graph = parse_graph(project.graph) 493 | 494 | for f in files: 495 | path = os.path.abspath(f) 496 | print(f, ':') 497 | for dep in sorted(graph.find_deps(path)): 498 | skip = False 499 | for dir in ['/proc/', '/tmp/', '/dev/']: 500 | if dep.startswith(dir): 501 | skip = True 502 | break 503 | if dep == path or skip or not project.is_output(dep): 504 | continue 505 | if dep.startswith(project.projectPath): 506 | dep = dep[len(project.projectPath) + 1:] 507 | print(' ', dep) 508 | 509 | 510 | def list_files(project, files): 511 | """Lists the files in the project to be fuzzed.""" 512 | 513 | inputs, outputs, built_by, graph = parse_graph(project.graph) 514 | if len(files) == 0: 515 | fuzzed = sorted([f for f in inputs - outputs if project.filter_in(f)]) 516 | else: 517 | fuzzed = [os.path.abspath(f) for f in files] 518 | 519 | count = len(fuzzed) 520 | for idx, input in zip(range(count), fuzzed): 521 | print(input) 522 | 523 | 524 | def parse_test(project, path): 525 | """Compares the dynamic graph to the parsed one.""" 526 | 527 | inputs, outputs, built_by, graph = parse_graph(project.graph) 528 | 529 | fuzzed = sorted([f for f in inputs - outputs if project.filter_in(f)]) 530 | count = len(fuzzed) 531 | 532 | root = project.buildPath 533 | 534 | G = defaultdict(list) 535 | with open(path, 'r') as f: 536 | for line in f.readlines(): 537 | src, deps = line.strip().split(':') 538 | src = os.path.normpath(os.path.join(root, src)) 539 | for dep in (w.strip() for w in deps.split(', ')): 540 | G[os.path.normpath(os.path.join(root, dep))].append(src) 541 | 542 | def traverse_graph(node, viz): 543 | if node in viz: 544 | return viz 545 | 546 | for next in G[node]: 547 | viz.add(node) 548 | traverse_graph(next, viz) 549 | return viz 550 | 551 | for idx, input in zip(range(count), fuzzed): 552 | print('[{0}/{1}] {2}:'.format(idx + 1, count, input)) 553 | 554 | expected = graph.find_deps(input) & outputs 555 | actual = traverse_graph(input, set()) 556 | if actual != expected: 557 | for f in sorted(actual): 558 | if f not in expected: 559 | print(' +', f) 560 | 561 | for f in sorted(expected): 562 | if f not in actual: 563 | print(' -', f) 564 | 565 | def race_test(project): 566 | """Test for race conditions.""" 567 | 568 | inputs, outputs, built_by, graph = parse_graph(project.graph) 569 | fuzzed = {f for f in outputs & inputs if project.filter_tmp(f)} 570 | 571 | # Create a copy of the graph from which missing edges will be removed. 572 | build_graph = defaultdict(set) 573 | for f, node in graph.nodes.iteritems(): 574 | for next in node.edges: 575 | build_graph[next].add(f) 576 | 577 | project.clean() 578 | project.build() 579 | 580 | missing_edges = [] 581 | for input in sorted(fuzzed): 582 | deps = graph.find_deps(input) 583 | if len(deps) == 1 and input in deps: 584 | continue 585 | 586 | # Touch the file, run the incremental build and read timestamps. 587 | t0 = read_mtimes(outputs) 588 | with project.touch(input): project.build() 589 | t1 = read_mtimes(outputs) 590 | 591 | # Find the set of changed files. 592 | modified = set() 593 | for k, v in t0.iteritems(): 594 | if v != t1[k] and project.is_output(k): 595 | modified.add(k) 596 | 597 | # Reset the project. 598 | reset_project(outputs) 599 | 600 | # Find expected changes. 601 | deps = graph.find_deps(input) 602 | expected = {f for f in deps & outputs if project.is_output(f)} 603 | 604 | if modified != expected: 605 | missing = expected - modified 606 | 607 | # Report differences. 608 | for f in {f for f in missing if graph.is_direct(input, f)}: 609 | if project.filter_tmp(f): 610 | missing_edges.append((input, f)) 611 | build_graph[f].remove(input) 612 | 613 | # Find the best and worst time a node can be scheduled in the build graph. 614 | graphs = {} 615 | build_graphs = {} 616 | race_files = set() 617 | for node in graph.topo_order(): 618 | self = {node} if node in outputs else set() 619 | 620 | graphs[node] = self.union( 621 | *[graphs.get(p, set()) for p in graph.rev_nodes[node].edges] 622 | ) 623 | build_graphs[node] = self.union( 624 | *[build_graphs.get(p, set()) for p in build_graph[node]] 625 | ) 626 | 627 | if graphs[node] > build_graphs[node]: 628 | race_files.add(node) 629 | 630 | print('Races:') 631 | for f in graph.prune_transitive(race_files): 632 | print(f, built_by[f]) 633 | 634 | print('Missing edges:') 635 | for src, dst in missing_edges: 636 | print(src, ' -> ', dst) 637 | 638 | 639 | 640 | def get_project(root, args): 641 | """Identifies the type of the project.""" 642 | 643 | graph = args.graph_path 644 | rule_path = args.rule_path 645 | use_hash = args.use_hash 646 | argv = args.argv.split(',') if args.argv else [] 647 | 648 | # CMake builds. 649 | if os.path.isfile(os.path.join(root, 'CMakeCache.txt')): 650 | projectDir = os.path.normpath(os.path.join(root, os.pardir)) 651 | if os.path.isfile(os.path.join(projectDir, 'CMakeLists.txt')): 652 | # Out of source. 653 | if os.path.isfile(os.path.join(root, 'Makefile')): 654 | return CMakeMake(projectDir, root, graph, rule_path, use_hash, argv) 655 | if os.path.isfile(os.path.join(root, 'build.ninja')): 656 | return CMakeNinja(projectDir, root, graph, rule_path, use_hash, argv) 657 | else: 658 | # In-source. 659 | if os.path.isfile(os.path.join(root, 'Makefile')): 660 | return CMakeMake(root, root, graph, rule_path, use_hash, argv) 661 | if os.path.isfile(os.path.join(root, 'build.ninja')): 662 | return CMakeNinja(root, root, graph, rule_path, use_hash, argv) 663 | 664 | # Manual GNU Make build. 665 | if os.path.isfile(os.path.join(root, 'Makefile')): 666 | return Make(root, graph, rule_path, use_hash, argv) 667 | 668 | # SCons build. 669 | if os.path.isfile(os.path.join(root, 'SConstruct')): 670 | return SCons(root, graph, rule_path, use_hash, argv) 671 | 672 | raise RuntimeError('Unknown project type') 673 | 674 | 675 | def main(): 676 | parser = argparse.ArgumentParser(description='Build Fuzzer') 677 | 678 | parser.add_argument( 679 | '--graph-path', 680 | type=str, 681 | default='/tmp/mkcheck', 682 | help='Path to the graph file' 683 | ) 684 | parser.add_argument( 685 | 'cmd', 686 | metavar='COMMAND', 687 | type=str, 688 | help='Command (build/fuzz/query/list/parse/race)' 689 | ) 690 | parser.add_argument( 691 | 'files', 692 | metavar='FILES', 693 | type=str, 694 | nargs='*', 695 | help='Input files' 696 | ) 697 | parser.add_argument( 698 | '--rule-path', 699 | type=str, 700 | help='Path to the rule file' 701 | ) 702 | parser.add_argument( 703 | '--use-hash', 704 | action='store_true', 705 | help='Change content hashes instead of timestamps' 706 | ) 707 | parser.add_argument( 708 | '--argv', 709 | type=str, 710 | default='', 711 | help='Additional arguments to make' 712 | ) 713 | 714 | args = parser.parse_args() 715 | 716 | buildDir = os.getcwd() 717 | project = get_project(buildDir, args) 718 | 719 | build_tool() 720 | 721 | if args.cmd == 'build': 722 | project.clean_build() 723 | return 724 | if args.cmd == 'fuzz': 725 | fuzz_test(project, args.files) 726 | return 727 | if args.cmd == 'query': 728 | query(project, args.files) 729 | return 730 | if args.cmd == 'list': 731 | list_files(project, args.files) 732 | return 733 | if args.cmd == 'parse': 734 | parse_test(project, args.files[0]) 735 | return 736 | if args.cmd == 'race': 737 | race_test(project) 738 | return 739 | 740 | raise RuntimeError('Unknown command: ' + args.cmd) 741 | 742 | 743 | 744 | if __name__ == '__main__': 745 | try: 746 | # Graphs traversals use dfs - raise stack limit here. 747 | resource.setrlimit(resource.RLIMIT_STACK, (2 ** 29, -1)) 748 | sys.setrecursionlimit(10 ** 6) 749 | except: 750 | print('WARNING: cannot set rlimit') 751 | main() 752 | -------------------------------------------------------------------------------- /tools/fuzz_test/graph.py: -------------------------------------------------------------------------------- 1 | # This file is part of the mkcheck project. 2 | # Licensing information can be found in the LICENSE file. 3 | # (C) 2018 Nandor Licker. ALl rights reserved. 4 | 5 | import os 6 | import json 7 | from collections import defaultdict 8 | 9 | 10 | 11 | class DependencyGraph(object): 12 | """Graph describing dependencies between file paths.""" 13 | 14 | class Node(object): 15 | def __init__(self, path): 16 | self.path = path 17 | self.edges = set() 18 | 19 | def __init__(self): 20 | self.nodes = {} 21 | self.rev_nodes = {} 22 | 23 | def add_dependency(self, src, dst): 24 | if src not in self.nodes: 25 | self.nodes[src] = self.Node(src) 26 | self.rev_nodes[src] = self.Node(src) 27 | if dst not in self.nodes: 28 | self.nodes[dst] = self.Node(dst) 29 | self.rev_nodes[dst] = self.Node(dst) 30 | self.nodes[src].edges.add(dst) 31 | self.rev_nodes[dst].edges.add(src) 32 | 33 | def find_deps(self, src): 34 | deps = set() 35 | def traverse(name): 36 | if name in deps: 37 | return 38 | deps.add(name) 39 | if name in self.nodes: 40 | for edge in self.nodes[name].edges: 41 | traverse(edge) 42 | traverse(src) 43 | return deps 44 | 45 | def is_direct(self, src, dst): 46 | return dst in self.nodes[src].edges 47 | 48 | def prune_transitive(self, nodes): 49 | non_transitive = nodes 50 | for node in nodes: 51 | if node not in non_transitive: 52 | continue 53 | non_transitive = non_transitive - (self.find_deps(node) - {node}) 54 | return non_transitive 55 | 56 | def topo_order(self): 57 | """Finds the first and last position a node can be scheduled to.""" 58 | 59 | topo = [] 60 | visited = set() 61 | def topo_dfs(node): 62 | if node in visited: 63 | return 64 | visited.add(node) 65 | 66 | for next in self.nodes[node].edges: 67 | topo_dfs(next) 68 | topo.append(node) 69 | 70 | for node in self.nodes.keys(): 71 | topo_dfs(node) 72 | 73 | return reversed(topo) 74 | 75 | 76 | def parse_graph(path): 77 | """Finds files written and read during a clean build.""" 78 | 79 | # Find all files and processes. 80 | files = {} 81 | inputs = set() 82 | outputs = set() 83 | built_by = {} 84 | with open(path, 'r') as f: 85 | data = json.loads(f.read()) 86 | for file in data["files"]: 87 | files[file['id']] = file 88 | for proc in data["procs"]: 89 | proc_in = set(proc.get('input', [])) 90 | proc_out = set(proc.get('output', [])) 91 | 92 | inputs = inputs | proc_in 93 | outputs = outputs | proc_out 94 | image = os.path.basename(files[proc['image']]['name']) 95 | for output in proc_out: 96 | built_by[files[output]['name']] = image 97 | 98 | def persisted(uid): 99 | if files[uid].get('deleted', False): 100 | return False 101 | if not files[uid].get('exists', False): 102 | return False 103 | name = files[uid]['name'] 104 | if name.startswith('/dev') or name.startswith('/proc'): 105 | return False 106 | return os.path.exists(name) and not os.path.isdir(name) 107 | 108 | inputs = {files[uid]['name'] for uid in inputs if persisted(uid)} 109 | outputs = {files[uid]['name'] for uid in outputs if persisted(uid)} 110 | 111 | gid = {} 112 | for proc in sorted(data["procs"], key=lambda p: p["uid"]): 113 | uid = proc["uid"] 114 | if proc.get('cow', False) and proc["parent"] in gid: 115 | gid[uid] = gid[proc["parent"]] 116 | else: 117 | gid[uid] = uid 118 | 119 | groups = defaultdict(lambda: (set(), set())) 120 | for proc in data["procs"]: 121 | group_id = gid[proc["uid"]] 122 | 123 | ins, outs = groups[group_id] 124 | ins.update(proc.get('input', [])) 125 | outs.update(proc.get('output', [])) 126 | 127 | edges = defaultdict(list) 128 | for uid, file in files.items(): 129 | for dep in file.get('deps', []): 130 | edges[files[dep]['name']].append(files[uid]['name']) 131 | 132 | for _, (ins, outs) in groups.items(): 133 | for input in ins - outs: 134 | if files[input]['name'] in ['/dev/stderr', '/dev/stdout']: 135 | continue 136 | if os.path.isdir(files[input]['name']): 137 | continue 138 | for output in outs: 139 | if files[output]['name'] in ['/dev/stderr', '/dev/stdout']: 140 | continue 141 | if os.path.isdir(files[output]['name']): 142 | continue 143 | edges[files[input]['name']].append(files[output]['name']) 144 | 145 | nodes = inputs | outputs 146 | 147 | graph = DependencyGraph() 148 | for src in nodes: 149 | visited = set() 150 | def add_edges(to): 151 | if to in visited: 152 | return 153 | visited.add(to) 154 | for node in edges.get(to, []): 155 | if node in nodes: 156 | if src != node: 157 | graph.add_dependency(src, node) 158 | else: 159 | add_edges(node) 160 | add_edges(src) 161 | 162 | return inputs, outputs, built_by, graph 163 | -------------------------------------------------------------------------------- /tools/fuzz_test/mtime.py: -------------------------------------------------------------------------------- 1 | # This file is part of the mkcheck project. 2 | # Licensing information can be found in the LICENSE file. 3 | # (C) 2017 Nandor Licker. All rights reserved. 4 | 5 | import os 6 | 7 | 8 | 9 | def read_mtimes(paths): 10 | mtimes = {} 11 | for path in paths: 12 | if os.path.exists(path): 13 | mtimes[path] = os.path.getmtime(path) 14 | else: 15 | mtimes[path] = 0 16 | return mtimes 17 | -------------------------------------------------------------------------------- /tools/fuzz_test/proc.py: -------------------------------------------------------------------------------- 1 | # This file is part of the mkcheck project. 2 | # Licensing information can be found in the LICENSE file. 3 | # (C) 2018 Nandor Licker. ALl rights reserved. 4 | 5 | import os 6 | import subprocess 7 | import sys 8 | 9 | 10 | def run_proc(*args, **kwargs): 11 | sys.stdout.flush() 12 | 13 | proc = subprocess.Popen( 14 | stdout=subprocess.PIPE, 15 | stderr=subprocess.PIPE, 16 | *args, 17 | **kwargs 18 | ) 19 | stdout, stderr = proc.communicate() 20 | if proc.returncode != 0: 21 | print(stdout, '\n', stderr) 22 | sys.stdout.flush() 23 | raise Exception('Command "%s" failed: %d' % (' '.join(args[0]), proc.returncode)) 24 | --------------------------------------------------------------------------------