diff --git a/CMakeLists.txt b/CMakeLists.txt index a074bb9..394ecfb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,9 @@ include(FetchContent) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-undefined,dynamic_lookup") option(BUILD_TESTS "Build tests" ON) option(BUILD_EXAMPLES "Build examples" ON) @@ -31,7 +34,7 @@ FetchContent_MakeAvailable(tracy) message(STATUS "${PROJECT_NAME} Building library") add_library(${PROJECT_NAME} INTERFACE) target_include_directories(${PROJECT_NAME} INTERFACE include ${Boost_INCLUDE_DIRS}) -target_link_libraries(${PROJECT_NAME} INTERFACE ${Boost_LIBRARIES} Tracy::TracyClient) +target_link_libraries(${PROJECT_NAME} INTERFACE dl ${Boost_LIBRARIES} Tracy::TracyClient) if (BUILD_EXAMPLES) add_subdirectory(examples) diff --git a/README.md b/README.md index 6091d62..205f6f5 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,23 @@ and the underlying implementation of the asynchronous interface. - Multiple backends: `kqueue`, `epoll`, `io_uring` (all of these seem to be edge triggered) - Supports unix domain sockets - Support for pinning event loop to hardware threads. +- `glibc` system call hooks -## Todo +## Usage -Both kqueue and epoll have this. Once the IO is complete, the callback is invoked. +Loom requires that you hook your main method (e.g the same watch Catch2 does) in order to intercept system calls before +`glibc` functions are invoked. This has various purposes, mainly for proper scheduling behaviour and telemetry. For +example, we don't want a `sleep` in the fiber thread to block the entire thread. Note that all macros in `loom` start +with `$`. -https://webflow.com/made-in-webflow/website/Apple-Style-Grid Can make this as the front page LOL +```cpp +#include + +$main(int argc, char* argv[]) { + // do stuff + return 0; +} +``` ## Backends diff --git a/docs/NOTES.md b/docs/NOTES.md index c99e9fe..308a4eb 100644 --- a/docs/NOTES.md +++ b/docs/NOTES.md @@ -5,6 +5,7 @@ backends: `kqueue`,`epoll`, `IOCP` ## `kqueue` (Darwin) +[Bible](https://people.freebsd.org/~jlemon/papers/kqueue.pdf) `KV_CLEAR`: Prevents kq from signalling the same event over and over again. If a socket wasn't read fully, then kq will signal us again, so instead of having to process the same signal multiple times, we make sure that we fully consume the data. @@ -25,8 +26,14 @@ struct kevent { }; ``` +https://stackoverflow.com/questions/37731435/what-exactly-is-kqueues-ev-receipt-for +EV_RECEIPT + ## `io_uring` (Linux) +https://github.com/axboe/liburing/issues/536 +https://github.com/axboe/liburing/issues/189 + ## `epoll` (Older Linux distributions) # Boost.Context diff --git a/examples/fibers.cpp b/examples/fibers.cpp index b48da90..b9f3dac 100644 --- a/examples/fibers.cpp +++ b/examples/fibers.cpp @@ -1,6 +1,7 @@ #include #include "loom/all.hpp" +#include using namespace std; @@ -16,7 +17,7 @@ struct Worker : loom::Fiber { } }; -int main() { +$LOOM_MAIN(int argc, char *argv[]) { Worker worker(4096); worker.start(); loom::Event fake{loom::Event::Type::NA, 0}; @@ -24,4 +25,5 @@ int main() { while (worker.resume(&fake)) { cout << "Resuming worker" << endl; } + return 0; } diff --git a/examples/monitor.cpp b/examples/monitor.cpp index 567805a..fff71ce 100644 --- a/examples/monitor.cpp +++ b/examples/monitor.cpp @@ -14,7 +14,7 @@ class Monitor : public loom::Fiber { } }; -int main(int argc, char *argv[]) { +$LOOM_MAIN(int argc, char *argv[]) { LOOM_ASSERT(argc == 2, "Usage: monitor "); Monitor monitor(4096); monitor.start(); diff --git a/examples/timer.cpp b/examples/timer.cpp index 8229835..1d118c9 100644 --- a/examples/timer.cpp +++ b/examples/timer.cpp @@ -19,7 +19,7 @@ class Worker : public loom::Fiber { } }; -int main() { +$LOOM_MAIN(int argc, char *argv[]) { Worker worker(4096); worker.start(); diff --git a/include/loom/all.hpp b/include/loom/all.hpp index 0d92680..98ccb67 100644 --- a/include/loom/all.hpp +++ b/include/loom/all.hpp @@ -4,5 +4,6 @@ #include #include #include +#include #include #include diff --git a/include/loom/hooks.hpp b/include/loom/hooks.hpp new file mode 100644 index 0000000..f029271 --- /dev/null +++ b/include/loom/hooks.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include + +#define $LOOM_MAIN(a, b) \ + int __main(int, char **); \ + int main(int argc, char *argv[]) { \ + hook_libc(); \ + return __main(argc, argv); \ + } \ + int __main(a, b) + +#define $HOOK_DECLARE(func, ret, ...) \ + typedef ret (*func##_t)(__VA_ARGS__); \ + __attribute__((unused)) static func##_t original_##func = nullptr + +#define HOOK_CHECK(name) \ + do { \ + if (original_##name == nullptr) { \ + original_##name = (decltype(original_##name))dlsym(RTLD_NEXT, #name); \ + if (original_##name == nullptr) { \ + const char *error = dlerror(); \ + if (error != nullptr) { \ + fprintf(stderr, "Hook failed for %s: %s\n", #name, error); \ + } else { \ + fprintf(stderr, "Hook failed for %s: unknown error\n", #name); \ + } \ + exit(-1); \ + } \ + } \ + } while (0) + +extern "C" { +__attribute__((unused)) const char *libc_name = "/usr/lib/libSystem.B.dylib"; +__attribute__((unused)) void *libc = nullptr; + +void hook_libc() { + if (libc == nullptr) { + libc = RTLD_NEXT; + if (libc == nullptr) { + fprintf(stderr, "ERROR: open %s failed.\n", libc_name); + std::exit(-1); + } + } +} +$HOOK_DECLARE(read, ssize_t, int fd, void *buf, size_t count); +$HOOK_DECLARE(write, ssize_t, int fd, const void *buf, size_t count); +$HOOK_DECLARE(malloc, void *, size_t size); +$HOOK_DECLARE(mmap, void *, void *addr, size_t length, int prot, int flags, int fd, + off_t offset); + +ssize_t write(int fd, const void *buf, size_t count) { + HOOK_CHECK(write); + printf("Write hook called\n"); + return original_write(fd, buf, count); +} +void *malloc(size_t size) { + HOOK_CHECK(malloc); + printf("Malloc hook called\n"); + return original_malloc(size); +} + +void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) { + HOOK_CHECK(mmap); + // PRint out details about mmap + printf("Size: %zu\n", length); + return original_mmap(addr, length, prot, flags, fd, offset); +} +} \ No newline at end of file