├── tests └── http_head │ ├── README │ ├── Makefile │ ├── fake_slow_http_server.py │ ├── setup.py │ ├── mod_http_head.c │ └── test.py ├── req.txt ├── MANIFEST.in ├── tox.ini ├── misc ├── test.sh └── build-wheels.sh ├── .gitignore ├── include ├── hook.h ├── elf_hook.h ├── hook_greenify.h └── libgreenify.h ├── .github └── workflows │ ├── test.yml │ └── publish.yml ├── src ├── hook.c ├── hook_greenify.c ├── libgreenify.c └── elf_hook.c ├── .travis.yml.bak ├── LICENSE ├── setup.py ├── README.rst └── greenify.pyx /tests/http_head/README: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /req.txt: -------------------------------------------------------------------------------- 1 | cython 2 | gevent 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft include 2 | include README.rst 3 | include LICENSE 4 | recursive-exclude tests * 5 | -------------------------------------------------------------------------------- /tests/http_head/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | 3 | all: test 4 | 5 | build: 6 | python setup.py build_ext --inplace 7 | 8 | test: build 9 | python test.py 10 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py39, py310, py311, py312, py313, py314 3 | 4 | [testenv] 5 | passenv = CC LD 6 | deps = -rreq.txt 7 | commands = {toxinidir}/misc/test.sh 8 | -------------------------------------------------------------------------------- /misc/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | cd tests/http_head 5 | python fake_slow_http_server.py& 6 | pid=$! 7 | pip install . 8 | python test.py 9 | kill $pid || true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeCache.txt 2 | CMakeFiles/ 3 | cmake_install.cmake 4 | *.o 5 | *.dylib 6 | *.so 7 | *.sw[op] 8 | *.py[co] 9 | greenify.c 10 | install_manifest.txt 11 | build/ 12 | dist/ 13 | venv/ 14 | *.egg-info/ 15 | *.egg 16 | .tox/ 17 | -------------------------------------------------------------------------------- /include/hook.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" 5 | { 6 | #endif 7 | 8 | void* hook(const char* library_filename, const char* function_name, const void* substitution_address); 9 | 10 | #ifdef __cplusplus 11 | } 12 | #endif 13 | 14 | -------------------------------------------------------------------------------- /misc/build-wheels.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e -x 3 | 4 | PACKAGE=$1 5 | PYENV=$2 6 | 7 | 8 | # Install a system package required by our library 9 | # Compile wheels 10 | for PYBIN in /opt/python/${PYENV}*/bin; do 11 | ${PYBIN}/pip install -r /io/req.txt 12 | ${PYBIN}/pip wheel /io/ -w wheelhouse/ 13 | done 14 | 15 | # Bundle external shared libraries into the wheels 16 | for whl in wheelhouse/${PACKAGE}*.whl; do 17 | auditwheel repair $whl --plat $PLAT -w /io/dist/ 18 | done 19 | -------------------------------------------------------------------------------- /include/elf_hook.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on Anthony Shoumikhin's article: http://www.codeproject.com/Articles/70302/Redirecting-functions-in-shared-ELF-libraries 3 | */ 4 | #pragma once 5 | 6 | #define LIBRARY_ADDRESS_BY_HANDLE(x) ((NULL == x) ? NULL : (void *)*(size_t const *)(x)) //undocumented hack to get shared library's address in memory by its handle 7 | 8 | #ifdef __cplusplus 9 | extern "C" 10 | { 11 | #endif 12 | 13 | void *elf_hook(char const *library_filename, void const *library_address, char const *function_name, void const *substitution_address); 14 | 15 | #ifdef __cplusplus 16 | } 17 | #endif 18 | -------------------------------------------------------------------------------- /include/hook_greenify.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hook.h" 4 | #include "libgreenify.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" 8 | { 9 | #endif 10 | 11 | typedef enum 12 | { 13 | FN_CONNECT, 14 | FN_READ, 15 | FN_WRITE, 16 | FN_PREAD, 17 | FN_PWRITE, 18 | FN_READV, 19 | FN_WRITEV, 20 | FN_RECV, 21 | FN_SEND, 22 | FN_RECVMSG, 23 | FN_SENDMSG, 24 | FN_RECVFROM, 25 | FN_SENDTO, 26 | FN_SELECT, 27 | FN_POLL, 28 | } greenified_function_t; 29 | 30 | void* greenify_patch_lib(const char* library_filename, greenified_function_t fn); 31 | 32 | #ifdef __cplusplus 33 | } 34 | #endif 35 | 36 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | unittest: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | pyver: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Set up Python 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ matrix.pyver }} 22 | - name: Install python dependencies 23 | run: | 24 | python -m pip install --upgrade pip setuptools 25 | pip install 'Cython' 'tox<4' 26 | 27 | - name: Run unittest 28 | run: | 29 | version=${{ matrix.pyver }} 30 | tox -e py${version/./} 31 | -------------------------------------------------------------------------------- /tests/http_head/fake_slow_http_server.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import time 4 | 5 | try: 6 | from SimpleHTTPServer import SimpleHTTPRequestHandler 7 | from SocketServer import TCPServer 8 | except ImportError: 9 | from http.server import SimpleHTTPRequestHandler 10 | from socketserver import TCPServer 11 | 12 | 13 | PORT = 0x2304 14 | BLOCKING_SECONDS = 10 # seconds 15 | 16 | 17 | class Server(TCPServer): 18 | allow_reuse_address = True 19 | 20 | 21 | class Handler(SimpleHTTPRequestHandler): 22 | def do_HEAD(self): 23 | time.sleep(BLOCKING_SECONDS) 24 | return SimpleHTTPRequestHandler.do_HEAD(self) 25 | 26 | 27 | if __name__ == "__main__": 28 | httpd = Server(("", PORT), Handler) 29 | try: 30 | httpd.serve_forever() 31 | except Exception: 32 | httpd.server_close() 33 | -------------------------------------------------------------------------------- /tests/http_head/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | from distutils.core import setup, Extension 4 | 5 | virtualenv_path = os.environ.get("VIRTUAL_ENV") 6 | 7 | include_dirs = list(filter(None, [os.environ.get("INCLUDE_DIRS")])) 8 | library_dirs = list(filter(None, [os.environ.get("LIBRARY_DIRS")])) 9 | 10 | if virtualenv_path: 11 | include_dirs.append("%s/include" % virtualenv_path) 12 | library_dirs.append("%s/lib" % virtualenv_path) 13 | ld_lib_key = "DYLD_LIBRARY_PATH" if platform.platform() == "Darwin" else "LD_LIBRARY_PATH" 14 | os.environ[ld_lib_key] = "%s/lib" % virtualenv_path 15 | 16 | mod_http_head = Extension( 17 | "mod_http_head", 18 | sources=["mod_http_head.c"], 19 | include_dirs=include_dirs, 20 | library_dirs=library_dirs, 21 | ) 22 | 23 | setup( 24 | name="mod_http_head", 25 | version="1.0", 26 | description="A demo package http_head greenified", 27 | ext_modules=[mod_http_head], 28 | ) 29 | -------------------------------------------------------------------------------- /src/hook.c: -------------------------------------------------------------------------------- 1 | #include "hook.h" 2 | #include 3 | 4 | #ifdef __ELF__ 5 | 6 | #include "elf_hook.h" 7 | #include 8 | 9 | #ifdef __cplusplus 10 | extern "C" 11 | { 12 | #endif 13 | 14 | void* hook(const char* library_filename, const char* function_name, const void* substitution_address) 15 | { 16 | void *handle, *address; 17 | if (NULL == library_filename) 18 | return NULL; 19 | 20 | handle = dlopen(library_filename, RTLD_LAZY); 21 | if (NULL == handle) 22 | return NULL; 23 | 24 | address = LIBRARY_ADDRESS_BY_HANDLE(handle); 25 | return elf_hook(library_filename, address, function_name, substitution_address); 26 | } 27 | 28 | #ifdef __cplusplus 29 | } 30 | #endif 31 | 32 | #else 33 | /* 34 | * Only ELF hook is supported now. 35 | */ 36 | 37 | #ifdef __cplusplus 38 | extern "C" 39 | { 40 | #endif 41 | 42 | void* hook(const char* library_filename, const char* function_name, const void* substitution_address) 43 | { 44 | return NULL; 45 | } 46 | 47 | #ifdef __cplusplus 48 | } 49 | #endif 50 | 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /.travis.yml.bak: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '2.7' 4 | - '3.5' 5 | - '3.6' 6 | - '3.7' 7 | - '3.8' 8 | - '3.9' 9 | - '3.10' 10 | services: docker 11 | sudo: required 12 | cache: 13 | bundler: true 14 | directories: 15 | - $HOME/.cache/pip 16 | - $HOME/docker 17 | env: 18 | - PLAT=manylinux2010_x86_64 19 | install: 20 | - if [[ -d $HOME/docker ]]; then ls $HOME/docker/*.tar.gz | xargs -I {file} sh -c "zcat {file} | docker load"; fi 21 | - docker pull quay.io/pypa/$PLAT 22 | - pip install -U pip setuptools 23 | - pip install Cython tox 24 | script: 25 | - tox -e py${TRAVIS_PYTHON_VERSION/./} && mkdir dist && docker run --rm -e PLAT=$PLAT -v `pwd`:/io quay.io/pypa/$PLAT /io/.travis/build-wheels.sh greenify cp${TRAVIS_PYTHON_VERSION/./} 26 | before_cache: 27 | - mkdir -p $HOME/docker && docker images -a --filter='dangling=false' --format '{{.Repository}}:{{.Tag}} {{.ID}}' | xargs -n 2 -t sh -c 'test -e $HOME/docker/$1.tar.gz || docker save $0 | gzip -2 > $HOME/docker/$1.tar.gz' 28 | deploy: 29 | provider: pypi 30 | skip_cleanup: true 31 | skip_existing: true 32 | user: windreamer 33 | password: 34 | secure: RITBiQqqC2dxKzFfhYeVkr9ZQGIaHV+sQifZKCAfNcbBKoh10ea37ZyF5JiGxeHf7v+gtPOa9ksO8PNwmZADLUln+qC1vO4LgSWB8P1GPPYabIKDDJB3Rvzv/cDDOlNPzcdIMb0L4STDFlI2NS5Q+STF0rJp0g0YrzrVCinBfSY= 35 | on: 36 | tags: true 37 | repo: douban/greenify 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009, Douban Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following disclaimer 13 | in the documentation and/or other materials provided with the 14 | distribution. 15 | 16 | * Neither the name of the Douban Inc. nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /src/hook_greenify.c: -------------------------------------------------------------------------------- 1 | #include "hook_greenify.h" 2 | 3 | #define _GREENIFY_PATCH_EXPAND(LIBPATH, FN) (hook((LIBPATH), #FN, (void*)green_##FN)) 4 | 5 | void* greenify_patch_lib(const char* library_filename, greenified_function_t fn) 6 | { 7 | switch (fn) 8 | { 9 | case FN_CONNECT: 10 | return _GREENIFY_PATCH_EXPAND(library_filename, connect); 11 | case FN_READ: 12 | return _GREENIFY_PATCH_EXPAND(library_filename, read); 13 | case FN_WRITE: 14 | return _GREENIFY_PATCH_EXPAND(library_filename, write); 15 | case FN_PREAD: 16 | return _GREENIFY_PATCH_EXPAND(library_filename, pread); 17 | case FN_PWRITE: 18 | return _GREENIFY_PATCH_EXPAND(library_filename, pwrite); 19 | case FN_READV: 20 | return _GREENIFY_PATCH_EXPAND(library_filename, readv); 21 | case FN_WRITEV: 22 | return _GREENIFY_PATCH_EXPAND(library_filename, writev); 23 | case FN_RECV: 24 | return _GREENIFY_PATCH_EXPAND(library_filename, recv); 25 | case FN_SEND: 26 | return _GREENIFY_PATCH_EXPAND(library_filename, send); 27 | case FN_RECVMSG: 28 | return _GREENIFY_PATCH_EXPAND(library_filename, recvmsg); 29 | case FN_SENDMSG: 30 | return _GREENIFY_PATCH_EXPAND(library_filename, sendmsg); 31 | case FN_RECVFROM: 32 | return _GREENIFY_PATCH_EXPAND(library_filename, recvfrom); 33 | case FN_SENDTO: 34 | return _GREENIFY_PATCH_EXPAND(library_filename, sendto); 35 | case FN_SELECT: 36 | return _GREENIFY_PATCH_EXPAND(library_filename, select); 37 | #ifndef NO_POLL 38 | case FN_POLL: 39 | return _GREENIFY_PATCH_EXPAND(library_filename, poll); 40 | #endif 41 | default: 42 | return NULL; 43 | } 44 | } 45 | 46 | #undef _GREENIFY_PATCH_EXPAND 47 | -------------------------------------------------------------------------------- /include/libgreenify.h: -------------------------------------------------------------------------------- 1 | #ifndef _GREENIFY_H 2 | #define _GREENIFY_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #ifndef NO_POLL 10 | #include 11 | #endif 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif 16 | 17 | int green_connect(int socket, const struct sockaddr *address, socklen_t address_len); 18 | 19 | ssize_t green_read(int fildes, void *buf, size_t nbyte); 20 | ssize_t green_write(int fildes, const void *buf, size_t nbyte); 21 | ssize_t green_pread(int fd, void *buf, size_t count, off_t offset); 22 | ssize_t green_pwrite(int fd, const void *buf, size_t count, off_t offset); 23 | ssize_t green_readv(int fd, const struct iovec *iov, int iovcnt); 24 | ssize_t green_writev(int fd, const struct iovec *iov, int iovcnt); 25 | 26 | ssize_t green_recv(int socket, void *buffer, size_t length, int flags); 27 | ssize_t green_send(int socket, const void *buffer, size_t length, int flags); 28 | ssize_t green_recvmsg(int socket, struct msghdr *message, int flags); 29 | ssize_t green_sendmsg(int socket, const struct msghdr *message, int flags); 30 | ssize_t green_recvfrom(int sockfd, void *buf, size_t len, int flags, 31 | struct sockaddr *src_addr, socklen_t *addrlen); 32 | ssize_t green_sendto(int sockfd, const void *buf, size_t len, int flags, 33 | const struct sockaddr *dest_addr, socklen_t addrlen); 34 | 35 | int green_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 36 | #ifndef NO_POLL 37 | int green_poll(struct pollfd *fds, nfds_t nfds, int timeout); 38 | #endif 39 | 40 | struct greenify_watcher { 41 | int fd; 42 | int events; 43 | }; 44 | 45 | /* return 0 for events occurred, -1 for timeout. 46 | * timeout in milliseconds */ 47 | typedef int (*greenify_wait_callback_func_t) (struct greenify_watcher watchers[], int nwatchers, int timeout); 48 | 49 | void greenify_set_wait_callback(greenify_wait_callback_func_t callback); 50 | 51 | #ifdef __cplusplus 52 | } 53 | #endif 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from glob import glob 2 | from setuptools import setup, Extension 3 | 4 | version = "0.5.0" 5 | 6 | 7 | def readme(): 8 | with open("README.rst") as f: 9 | return f.read() 10 | 11 | 12 | def make_limited_api_macro(version): 13 | s = 0 14 | step = 8 15 | pos = step * 3 16 | for i in version.split("."): 17 | s += int(i) << pos 18 | pos -= step 19 | return s 20 | 21 | 22 | include_dirs = ["include"] 23 | sources = glob("*.pyx") + glob("src/*.c") 24 | libraries = ["dl"] 25 | 26 | setup( 27 | name="greenify", 28 | version=version, 29 | description="Make C module compatible with gevent at runtime.", 30 | long_description=readme(), 31 | platforms=["Linux"], 32 | classifiers=[ 33 | "Intended Audience :: Developers", 34 | "Operating System :: POSIX :: Linux", 35 | "Programming Language :: C", 36 | "Programming Language :: Cython", 37 | "Programming Language :: Python :: 3", 38 | "Programming Language :: Python :: 3.9", 39 | "Programming Language :: Python :: 3.10", 40 | "Programming Language :: Python :: 3.11", 41 | "Programming Language :: Python :: 3.12", 42 | "Programming Language :: Python :: 3.13", 43 | "Programming Language :: Python :: 3.14", 44 | "Programming Language :: Python :: Implementation :: CPython", 45 | "Development Status :: 5 - Production/Stable", 46 | "Operating System :: POSIX :: Linux", 47 | "Intended Audience :: Developers", 48 | "License :: OSI Approved :: BSD License", 49 | "Topic :: Software Development :: Libraries", 50 | ], 51 | author="Zhongbo Tian", 52 | author_email="tianzhongbo@douban.com", 53 | url="https://github.com/douban/greenify", 54 | download_url="https://github.com/douban/greenify/archive/%s.tar.gz" % version, 55 | setup_requires=["Cython"], 56 | install_requires=["gevent"], 57 | ext_modules=[ 58 | Extension( 59 | "greenify", 60 | sources, 61 | include_dirs=include_dirs, 62 | libraries=libraries, 63 | define_macros=[ 64 | ("Py_LIMITED_API", make_limited_api_macro("3.9")), 65 | ], 66 | py_limited_api=True, 67 | ) 68 | ], 69 | ) 70 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | greenify_ 2 | ========= 3 | 4 | |build| |status| |pypiv| |pyversions| |wheel| |license| 5 | 6 | greenify_ can make Python extension modules having network operations in C 7 | code to be compatible with gevent_. 8 | 9 | greenify_ uses the Dynamic Function Redirecting technique same as ELF-Hook_ 10 | to patch blocking network operations at runtime, without the need modify 11 | the original modules. 12 | 13 | Currently greenify_ only supports ELF format modules, and is tested on Linux. 14 | 15 | 16 | Install from source 17 | ------------------- 18 | 19 | ``greenify`` module is installed using setuptools or pip:: 20 | 21 | python setup.py install 22 | 23 | or:: 24 | 25 | pip install greenify 26 | 27 | Usage 28 | ----- 29 | 30 | 1. Active greenify_ before initiate environment:: 31 | 32 | import greenify 33 | greenify.greenify() 34 | 35 | 2. Make sure the dynamic module(e.g. libmemcached) is patched before using:: 36 | 37 | assert greenify.patch_lib('/usr/lib/libmemcached.so') 38 | 39 | 3. Import and use the corresponding module, which is now gevent_ compatible. 40 | 41 | Thread Safety 42 | ------------- 43 | 44 | Once activated, the green C functions will, on potentially blocking operation, 45 | pass control to gevent's main event loop, which may switch to other ready 46 | greenlet which is also in one of the green functions. So, make sure your C 47 | code can handle this kind of execution pause and resume. A thread safe 48 | program usually is ready for greenify, but remember that all the switches 49 | happen in a single thread. 50 | 51 | 52 | License 53 | ------- 54 | 55 | greenify_ is written and maintained by `douban`_ and is licensed under New BSD license. 56 | 57 | 58 | .. _gevent: http://www.gevent.org 59 | .. _greenify: https://github.com/douban/greenify 60 | .. _douban: http://www.douban.com 61 | .. _ELF-Hook: https://github.com/shoumikhin/ELF-Hook 62 | 63 | .. |build| image:: https://github.com/douban/greenify/actions/workflows/test.yml/badge.svg 64 | :target: https://github.com/douban/greenify/actions/workflows/test.yml 65 | 66 | .. |pypiv| image:: https://img.shields.io/pypi/v/greenify 67 | :target: https://pypi.org/project/greenify/ 68 | 69 | .. |status| image:: https://img.shields.io/pypi/status/greenify 70 | .. |pyversions| image:: https://img.shields.io/pypi/pyversions/greenify 71 | .. |wheel| image:: https://img.shields.io/pypi/wheel/greenify 72 | .. |license| image:: https://img.shields.io/pypi/l/greenify?color=blue 73 | -------------------------------------------------------------------------------- /tests/http_head/mod_http_head.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | int c_http_head(const char *host, const char* port) 13 | { 14 | struct addrinfo *p, hints, *res; 15 | char request[64]; 16 | char response[2049]; 17 | int socket_fd; 18 | 19 | sprintf(request, "HEAD / HTTP/1.1\nhost: %s\n\n", host); 20 | 21 | memset(&hints, 0, sizeof(hints)); 22 | 23 | hints.ai_family = AF_UNSPEC; 24 | hints.ai_socktype = SOCK_STREAM; 25 | getaddrinfo(host, port, &hints, &res); 26 | for(p = res; p; p = p->ai_next) 27 | { 28 | socket_fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); 29 | if (connect(socket_fd, p->ai_addr, p->ai_addrlen) == 0) { 30 | // fprintf(stderr, "[%s] connected %s\n", __FUNCTION__, host); 31 | break; 32 | } 33 | } 34 | 35 | send(socket_fd, request, strlen(request), 0); 36 | recv(socket_fd, response, 2048, 0); 37 | close(socket_fd); 38 | // fprintf(stderr, "[%s] closed %s\n", __FUNCTION__, host); 39 | // fprintf(stderr, "%s\n", response); 40 | if (strstr(response, "HTTP/1.") != NULL) { // HTTP/1.1 or HTTP/1.0 41 | return 1; 42 | } else { 43 | fprintf(stderr, "[%s] unexpected response: %s\n", __FUNCTION__, response); 44 | } 45 | return 0; 46 | } 47 | 48 | 49 | static PyObject* 50 | http_head(PyObject* self, PyObject* args) 51 | { 52 | const char* host, *port; 53 | if (!PyArg_ParseTuple(args, "ss", &host, &port)) 54 | { 55 | return NULL; 56 | } 57 | long ret = c_http_head(host, port); 58 | #if PY_MAJOR_VERSION >= 3 59 | return PyLong_FromLong(ret); 60 | #else 61 | return PyInt_FromLong(ret); 62 | #endif 63 | } 64 | 65 | 66 | static PyMethodDef ModHttpHeadMethods[] = 67 | { 68 | {"http_head", http_head, METH_VARARGS, "A naive http head test."}, 69 | {NULL, NULL, 0, NULL} 70 | }; 71 | 72 | #if PY_MAJOR_VERSION >= 3 73 | static struct PyModuleDef moduledef = { 74 | PyModuleDef_HEAD_INIT, 75 | "mod_http_head", /* m_name */ 76 | "", /* m_doc */ 77 | -1, /* m_size */ 78 | ModHttpHeadMethods, /* m_methods */ 79 | NULL, /* m_reload */ 80 | NULL, /* m_traverse */ 81 | NULL, /* m_clear */ 82 | NULL, /* m_free */ 83 | }; 84 | 85 | PyMODINIT_FUNC 86 | PyInit_mod_http_head(void) 87 | { 88 | return PyModule_Create(&moduledef); 89 | } 90 | #else 91 | PyMODINIT_FUNC 92 | initmod_http_head(void) 93 | { 94 | (void) Py_InitModule("mod_http_head", ModHttpHeadMethods); 95 | } 96 | #endif 97 | -------------------------------------------------------------------------------- /tests/http_head/test.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import print_function 3 | 4 | import sys 5 | import time 6 | 7 | # greenify 8 | import greenify 9 | 10 | greenify.greenify() 11 | 12 | # python patch 13 | import gevent # noqa: E402 14 | import gevent.monkey # noqa: E402 15 | 16 | gevent.monkey.patch_all() 17 | 18 | import mod_http_head # noqa: E402 19 | 20 | assert greenify.patch_lib(mod_http_head.__file__) 21 | import fake_slow_http_server # noqa: E402 22 | 23 | stack = [] 24 | 25 | 26 | def c_http_head_check(addr): 27 | stack.append(("begin", addr, "c")) 28 | print("%.5f head %s begin" % (time.time(), addr), file=sys.stderr) 29 | ret = mod_http_head.http_head(*addr) 30 | print("%.5f head %s end" % (time.time(), addr), file=sys.stderr) 31 | stack.append(("end", addr, "c")) 32 | assert ret == 1 33 | 34 | 35 | def python_http_head_check(addr): 36 | try: 37 | from httplib import HTTPConnection 38 | except ImportError: 39 | from http.client import HTTPConnection 40 | 41 | stack.append(("begin", addr, "python")) 42 | print("%.5f head %s begin" % (time.time(), addr), file=sys.stderr) 43 | conn = HTTPConnection(*addr) 44 | conn.request("HEAD", "/") 45 | resp = conn.getresponse() 46 | status_code = resp.status 47 | print("%.5f head %s end" % (time.time(), addr), file=sys.stderr) 48 | stack.append(("end", addr, "python")) 49 | assert 200 <= status_code < 400 50 | 51 | 52 | def sleeper(): 53 | stack.append(("begin", "sleeper")) 54 | print("%.5f sleeper begin" % time.time(), file=sys.stderr) 55 | time.sleep(fake_slow_http_server.BLOCKING_SECONDS / 2) 56 | print("%.5f sleeper end" % time.time(), file=sys.stderr) 57 | stack.append(("end", "sleeper")) 58 | 59 | 60 | def main(): 61 | global stack 62 | local_addr = ("localhost", str(fake_slow_http_server.PORT)) 63 | test_sites = ( 64 | local_addr, 65 | ("google.com", "80"), 66 | ("twitter.com", "80"), 67 | ("douban.com", "80"), 68 | ("github.com", "80"), 69 | ) 70 | for fn in [python_http_head_check, c_http_head_check]: 71 | stack = [] 72 | print("test %s" % fn.__name__) 73 | t0 = time.time() 74 | workers = [gevent.spawn(fn, addr) for addr in test_sites] 75 | workers.append(gevent.spawn(sleeper)) 76 | 77 | gevent.joinall(workers) 78 | time_elapsed = time.time() - t0 79 | print("done in %.5fs" % (time_elapsed)) 80 | print() 81 | # HEAD to fake_slow_http_server is expected to 82 | # be the slowest worker 83 | assert stack[-1][:2] == ("end", local_addr), "unexpected: %r" % (stack[-1][:2],) 84 | assert time_elapsed < fake_slow_http_server.BLOCKING_SECONDS * 1.5 85 | 86 | 87 | if __name__ == "__main__": 88 | main() 89 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Set up Python 3.9 12 | uses: actions/setup-python@v2 13 | with: 14 | python-version: 3.9 15 | - name: Install python dependencies 16 | run: pip install Cython auditwheel 17 | - name: Build a source tarball 18 | run: python setup.py sdist 19 | - name: Build Wheel 20 | run: python setup.py bdist_wheel --py-limited-api=cp39 21 | - name: Repair Wheel 22 | run: find dist -name *.whl -print -exec auditwheel repair {} \; 23 | - name: Clean Non Audited Wheel 24 | run: rm -v dist/*-linux_x86_64.whl 25 | - name: Check build result 26 | run: | 27 | ls -lh dist 28 | find dist -name *.tar.gz -print -exec tar -ztvf {} \; 29 | ls -lh wheelhouse 30 | find wheelhouse -name *.whl -print -exec unzip -l {} \; 31 | - uses: actions/upload-artifact@v4 32 | with: 33 | name: wheel 34 | path: wheelhouse/*.whl 35 | - uses: actions/upload-artifact@v4 36 | with: 37 | name: sdist 38 | path: dist/*.tar.gz 39 | 40 | test: 41 | needs: [build] 42 | runs-on: ubuntu-latest 43 | strategy: 44 | matrix: 45 | pyver: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] 46 | 47 | steps: 48 | - uses: actions/checkout@v4 49 | with: 50 | # only checkout files for test 51 | sparse-checkout: | 52 | tests 53 | misc/test.sh 54 | - uses: actions/download-artifact@v4 55 | with: 56 | name: wheel 57 | path: wheel 58 | - name: Set up Python 59 | uses: actions/setup-python@v2 60 | with: 61 | python-version: ${{ matrix.pyver }} 62 | - name: Install python dependencies 63 | run: | 64 | pip install gevent 65 | pip install wheel/*.whl 66 | - name: Run test 67 | run: misc/test.sh 68 | 69 | pypi: 70 | needs: [build, test] 71 | runs-on: ubuntu-latest 72 | 73 | steps: 74 | - uses: actions/download-artifact@v4 75 | with: 76 | name: wheel 77 | path: dist 78 | - uses: actions/download-artifact@v4 79 | with: 80 | name: sdist 81 | path: dist 82 | - name: dump result 83 | run: | 84 | ls -lh dist 85 | find dist -name *.whl -print -exec unzip -l {} \; 86 | find dist -name *.tar.gz -print -exec tar -ztvf {} \; 87 | - name: Publish to PyPI 88 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') 89 | uses: pypa/gh-action-pypi-publish@release/v1 90 | with: 91 | skip_existing: true 92 | user: __token__ 93 | password: ${{ secrets.PYPI_API_TOKEN }} 94 | -------------------------------------------------------------------------------- /greenify.pyx: -------------------------------------------------------------------------------- 1 | from gevent.hub import get_hub, getcurrent, Waiter 2 | from gevent.timeout import Timeout 3 | 4 | cdef extern from "libgreenify.h": 5 | struct greenify_watcher: 6 | int fd 7 | int events 8 | ctypedef int (*greenify_wait_callback_func_t) (greenify_watcher* watchers, int nwatchers, int timeout) 9 | cdef void greenify_set_wait_callback(greenify_wait_callback_func_t callback) 10 | 11 | cdef extern from "hook_greenify.h": 12 | 13 | ctypedef enum greenified_function_t: 14 | FN_CONNECT 15 | FN_READ 16 | FN_WRITE 17 | FN_PREAD 18 | FN_PWRITE 19 | FN_READV 20 | FN_WRITEV 21 | FN_RECV 22 | FN_SEND 23 | FN_RECVMSG 24 | FN_SENDMSG 25 | FN_RECVFROM 26 | FN_SENDTO 27 | FN_SELECT 28 | FN_POLL 29 | 30 | void* greenify_patch_lib(const char* library_filename, greenified_function_t fn) 31 | 32 | cdef int wait_gevent(greenify_watcher* watchers, int nwatchers, int timeout_in_ms) noexcept with gil: 33 | cdef int fd, event 34 | cdef float timeout_in_s 35 | cdef int i 36 | 37 | hub = get_hub() 38 | watchers_list = [] 39 | for i in range(nwatchers): 40 | fd = watchers[i].fd 41 | event = watchers[i].events 42 | watcher = hub.loop.io(fd, event) 43 | watchers_list.append(watcher) 44 | 45 | if timeout_in_ms != 0: 46 | timeout_in_s = timeout_in_ms / 1000.0 47 | t = Timeout.start_new(timeout_in_s) 48 | try: 49 | wait(watchers_list) 50 | return 0 51 | except Timeout: 52 | return -1 53 | finally: 54 | t.cancel() 55 | else: 56 | wait(watchers_list) 57 | return 0 58 | 59 | def greenify(): 60 | greenify_set_wait_callback(wait_gevent) 61 | 62 | def wait(watchers): 63 | waiter = Waiter() 64 | switch = waiter.switch 65 | unique = object() 66 | try: 67 | count = len(watchers) 68 | for watcher in watchers: 69 | watcher.start(switch, unique) 70 | result = waiter.get() 71 | assert result is unique, 'Invalid switch into %s: %r' % (getcurrent(), result) 72 | waiter.clear() 73 | return result 74 | finally: 75 | for watcher in watchers: 76 | watcher.stop() 77 | 78 | cpdef patch_lib(library_path): 79 | cdef char* path 80 | if isinstance(library_path, unicode): 81 | library_path = (library_path).encode('utf8') 82 | 83 | path = library_path 84 | cdef bint result = False 85 | for fn in (FN_CONNECT, FN_READ, FN_WRITE, FN_PREAD, FN_PWRITE, FN_READV, 86 | FN_WRITEV, FN_RECV, FN_SEND, FN_RECVMSG, FN_SENDMSG, 87 | FN_RECVFROM, FN_SENDTO, FN_SELECT, FN_POLL): 88 | if NULL != greenify_patch_lib(path, fn): 89 | result = True 90 | 91 | return result 92 | -------------------------------------------------------------------------------- /src/libgreenify.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #ifdef DEBUG 12 | #include 13 | #define debug(...) \ 14 | do { \ 15 | time_t now = time(NULL); \ 16 | char t[30]; \ 17 | ctime_r(&now, t); \ 18 | t[24] = '\0'; \ 19 | fprintf(stderr, "[greenify] [%s] [%d] ", t, getpid()); \ 20 | fprintf(stderr, __VA_ARGS__); \ 21 | } while(0) 22 | #else /* #define DEBUG */ 23 | #define debug(...) 24 | #endif /* #define DEBUG */ 25 | 26 | #include "libgreenify.h" 27 | 28 | #define EVENT_READ 0x01 29 | #define EVENT_WRITE 0x02 30 | 31 | static greenify_wait_callback_func_t g_wait_callback = NULL; 32 | 33 | /* return 1 means the flags changed */ 34 | static int 35 | set_nonblock(int fd, int *old_flags) 36 | { 37 | *old_flags = fcntl(fd, F_GETFL, 0); 38 | if ((*old_flags) & O_NONBLOCK) { 39 | return 0; 40 | } else { 41 | fcntl(fd, F_SETFL, *old_flags | O_NONBLOCK); 42 | return 1; 43 | } 44 | } 45 | 46 | static void 47 | restore_flags(int fd, int flags) 48 | { 49 | fcntl(fd, F_SETFL, flags); 50 | } 51 | 52 | void greenify_set_wait_callback(greenify_wait_callback_func_t callback) 53 | { 54 | g_wait_callback = callback; 55 | } 56 | 57 | int callback_multiple_watchers(struct greenify_watcher* watchers, int nwatchers, int timeout) 58 | { 59 | int retval; 60 | assert(g_wait_callback != NULL); 61 | retval = g_wait_callback(watchers, nwatchers, timeout); 62 | return retval; 63 | } 64 | 65 | int callback_single_watcher(int fd, int events, int timeout) 66 | { 67 | struct greenify_watcher watchers[1]; 68 | int retval; 69 | 70 | assert(g_wait_callback != NULL); 71 | 72 | watchers[0].fd = fd; 73 | watchers[0].events = events; 74 | retval = g_wait_callback(watchers, 1, timeout); 75 | return retval; 76 | } 77 | static int 78 | is_not_socket(int fd) 79 | { 80 | int opt; 81 | socklen_t len = sizeof(opt); 82 | if(-1 == getsockopt(fd, SOL_SOCKET, SO_DEBUG, &opt, &len) && errno == ENOTSOCK) 83 | { 84 | errno = 0; 85 | return 1; 86 | } 87 | return 0; 88 | } 89 | 90 | int 91 | green_connect(int socket, const struct sockaddr *address, socklen_t address_len) 92 | { 93 | int flags, s_err, retval; 94 | 95 | debug("Enter green_connect\n"); 96 | 97 | if (g_wait_callback == NULL || !set_nonblock(socket, &flags)) { 98 | retval = connect(socket, address, address_len); 99 | return retval; 100 | } 101 | 102 | retval = connect(socket, address, address_len); 103 | s_err = errno; 104 | if (retval < 0 && (s_err == EWOULDBLOCK || s_err == EALREADY || s_err == EINPROGRESS)) { 105 | callback_single_watcher(socket, EVENT_WRITE, 0); 106 | getsockopt(socket, SOL_SOCKET, SO_ERROR, &s_err, &address_len); 107 | retval = s_err ? -1 : 0; 108 | } 109 | 110 | restore_flags(socket, flags); 111 | errno = s_err; 112 | return retval; 113 | } 114 | 115 | 116 | #define _IMPL_SOCKET_ONLY_GREEN_FN(FN, EV, FID, ...) \ 117 | do { \ 118 | int flags, s_err; \ 119 | ssize_t retval; \ 120 | \ 121 | debug("Enter green_" #FN "\n"); \ 122 | \ 123 | if (g_wait_callback == NULL || is_not_socket((FID)) || !set_nonblock((FID), &flags)) { \ 124 | return FN((FID), __VA_ARGS__); \ 125 | } \ 126 | \ 127 | do { \ 128 | retval = FN((FID), __VA_ARGS__); \ 129 | s_err = errno; \ 130 | debug(#FN ", return %zd, errno: %d\n", retval, s_err); \ 131 | } while (retval < 0 && (s_err == EWOULDBLOCK || s_err == EAGAIN) \ 132 | && !(retval = callback_single_watcher((FID), (EV), 0))); \ 133 | \ 134 | restore_flags((FID), flags); \ 135 | errno = s_err; \ 136 | return retval; \ 137 | } while(0) 138 | 139 | ssize_t green_read(int fildes, void *buf, size_t nbyte) 140 | { 141 | _IMPL_SOCKET_ONLY_GREEN_FN(read, EVENT_READ, fildes, buf, nbyte); 142 | } 143 | 144 | ssize_t green_write(int fildes, const void *buf, size_t nbyte) 145 | { 146 | _IMPL_SOCKET_ONLY_GREEN_FN(write, EVENT_WRITE, fildes, buf, nbyte); 147 | } 148 | 149 | ssize_t green_pread(int fd, void *buf, size_t count, off_t offset) 150 | { 151 | _IMPL_SOCKET_ONLY_GREEN_FN(pread, EVENT_READ, fd, buf, count, offset); 152 | } 153 | 154 | ssize_t green_pwrite(int fd, const void *buf, size_t count, off_t offset) 155 | { 156 | _IMPL_SOCKET_ONLY_GREEN_FN(pwrite, EVENT_WRITE, fd, buf, count, offset); 157 | } 158 | 159 | ssize_t green_readv(int fd, const struct iovec *iov, int iovcnt) 160 | { 161 | _IMPL_SOCKET_ONLY_GREEN_FN(readv, EVENT_READ, fd, iov, iovcnt); 162 | } 163 | 164 | ssize_t green_writev(int fd, const struct iovec *iov, int iovcnt) 165 | { 166 | _IMPL_SOCKET_ONLY_GREEN_FN(writev, EVENT_WRITE, fd, iov, iovcnt); 167 | } 168 | 169 | #undef _IMPL_SOCKET_ONLY_GREEN_FN 170 | 171 | 172 | #define _IMPL_GREEN_FN(FN, EV, FID, ...) \ 173 | do { \ 174 | int sock_flags, s_err; \ 175 | ssize_t retval; \ 176 | \ 177 | debug("Enter green_" #FN "\n"); \ 178 | \ 179 | if (g_wait_callback == NULL || !set_nonblock((FID), &sock_flags)) { \ 180 | return FN((FID), __VA_ARGS__); \ 181 | } \ 182 | \ 183 | do { \ 184 | retval = FN((FID), __VA_ARGS__); \ 185 | s_err = errno; \ 186 | debug(#FN ", return %zd, errno: %d\n", retval, s_err); \ 187 | } while (retval < 0 && (s_err == EWOULDBLOCK || s_err == EAGAIN) \ 188 | && !(retval = callback_single_watcher((FID), (EV), 0))); \ 189 | \ 190 | restore_flags((FID), sock_flags); \ 191 | errno = s_err; \ 192 | return retval; \ 193 | } while(0) 194 | 195 | ssize_t green_recv(int socket, void *buffer, size_t length, int flags) { 196 | _IMPL_GREEN_FN(recv, EVENT_READ, socket, buffer, length, flags); 197 | } 198 | 199 | ssize_t green_send(int socket, const void *buffer, size_t length, int flags) { 200 | _IMPL_GREEN_FN(send, EVENT_WRITE, socket, buffer, length, flags); 201 | } 202 | 203 | ssize_t green_recvmsg(int socket, struct msghdr *message, int flags) { 204 | _IMPL_GREEN_FN(recvmsg, EVENT_READ, socket, message, flags); 205 | } 206 | 207 | ssize_t green_sendmsg(int socket, const struct msghdr* message, int flags) { 208 | _IMPL_GREEN_FN(sendmsg, EVENT_WRITE, socket, message, flags); 209 | } 210 | 211 | ssize_t green_recvfrom(int sockfd, void *buf, size_t len, int flags, 212 | struct sockaddr *src_addr, socklen_t *addrlen) 213 | { 214 | _IMPL_GREEN_FN(recvfrom, EVENT_READ, sockfd, buf, len, flags, src_addr, addrlen); 215 | } 216 | 217 | ssize_t green_sendto(int sockfd, const void *buf, size_t len, int flags, 218 | const struct sockaddr *dest_addr, socklen_t addrlen) 219 | { 220 | _IMPL_GREEN_FN(sendto, EVENT_WRITE, sockfd, buf, len, flags, dest_addr, addrlen); 221 | } 222 | 223 | #undef _IMPL_GREEN_FN 224 | 225 | int 226 | green_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) 227 | { 228 | struct greenify_watcher watchers[nfds]; 229 | int count = 0, i = 0; 230 | 231 | debug("Enter green_select\n"); 232 | if (g_wait_callback == NULL) 233 | return select(nfds, readfds, writefds, exceptfds, timeout); 234 | 235 | for (i = 0; i < nfds; ++i) { 236 | if (FD_ISSET(i, readfds) || FD_ISSET(i, exceptfds)) { 237 | watchers[count].fd = i; 238 | watchers[count].events = EVENT_READ; 239 | count++; 240 | } 241 | if (FD_ISSET(i, writefds)) { 242 | watchers[count].fd = i; 243 | watchers[count].events = EVENT_WRITE; 244 | count++; 245 | } 246 | } 247 | 248 | float timeout_in_ms = timeout->tv_usec / 1000.0; 249 | callback_multiple_watchers(watchers, count, timeout_in_ms); 250 | return select(nfds, readfds, writefds, exceptfds, timeout); 251 | } 252 | 253 | #ifndef NO_POLL 254 | int 255 | green_poll(struct pollfd *fds, nfds_t nfds, int timeout) 256 | { 257 | nfds_t i; 258 | struct greenify_watcher watchers[nfds]; 259 | 260 | debug("Enter green_poll\n"); 261 | 262 | if (g_wait_callback == NULL || timeout == 0) 263 | return poll(fds, nfds, timeout); 264 | 265 | for (i = 0; i < nfds; i++) { 266 | if (fds[i].events & ~(POLLIN | POLLPRI | POLLOUT)) { 267 | fprintf(stderr, "[greenify] support POLLIN|POLLPRI|POLLOUT only, got 0x%x, may block.\n", 268 | fds[i].events); 269 | return poll(fds, nfds, timeout); 270 | } 271 | 272 | watchers[i].fd = fds[i].fd; 273 | watchers[i].events = 0; 274 | 275 | if (fds[i].events & POLLIN || fds[i].events & POLLPRI) { 276 | watchers[i].events |= EVENT_READ; 277 | } 278 | 279 | if (fds[i].events & POLLOUT) { 280 | watchers[i].events |= EVENT_WRITE; 281 | } 282 | } 283 | 284 | callback_multiple_watchers(watchers, nfds, timeout); 285 | return poll(fds, nfds, 0); 286 | } 287 | #endif 288 | -------------------------------------------------------------------------------- /src/elf_hook.c: -------------------------------------------------------------------------------- 1 | #ifdef __ELF__ 2 | /* 3 | * Based on Anthony Shoumikhin's article: http://www.codeproject.com/Articles/70302/Redirecting-functions-in-shared-ELF-libraries 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | //rename standart types for convenience 15 | #ifdef __x86_64 16 | #define Elf_Ehdr Elf64_Ehdr 17 | #define Elf_Shdr Elf64_Shdr 18 | #define Elf_Sym Elf64_Sym 19 | #define Elf_Rel Elf64_Rela 20 | #define ELF_R_SYM ELF64_R_SYM 21 | #define REL_DYN ".rela.dyn" 22 | #define REL_PLT ".rela.plt" 23 | #else 24 | #define Elf_Ehdr Elf32_Ehdr 25 | #define Elf_Shdr Elf32_Shdr 26 | #define Elf_Sym Elf32_Sym 27 | #define Elf_Rel Elf32_Rel 28 | #define ELF_R_SYM ELF32_R_SYM 29 | #define REL_DYN ".rel.dyn" 30 | #define REL_PLT ".rel.plt" 31 | #endif 32 | 33 | //================================================================================================== 34 | int read_header(int d, Elf_Ehdr **header) 35 | { 36 | *header = (Elf_Ehdr *)malloc(sizeof(Elf_Ehdr)); 37 | 38 | if (lseek(d, 0, SEEK_SET) < 0) 39 | { 40 | free(*header); 41 | 42 | return errno; 43 | } 44 | 45 | if (read(d, *header, sizeof(Elf_Ehdr)) <= 0) 46 | { 47 | free(*header); 48 | 49 | return errno = EINVAL; 50 | } 51 | 52 | return 0; 53 | } 54 | //-------------------------------------------------------------------------------------------------- 55 | int read_section_table(int d, Elf_Ehdr const *header, Elf_Shdr **table) 56 | { 57 | size_t size; 58 | 59 | if (NULL == header) 60 | return EINVAL; 61 | 62 | size = header->e_shnum * sizeof(Elf_Shdr); 63 | *table = (Elf_Shdr *)malloc(size); 64 | 65 | if (lseek(d, header->e_shoff, SEEK_SET) < 0) 66 | { 67 | free(*table); 68 | 69 | return errno; 70 | } 71 | 72 | if (read(d, *table, size) <= 0) 73 | { 74 | free(*table); 75 | 76 | return errno = EINVAL; 77 | } 78 | 79 | return 0; 80 | } 81 | //-------------------------------------------------------------------------------------------------- 82 | int read_string_table(int d, Elf_Shdr const *section, char const **strings) 83 | { 84 | if (NULL == section) 85 | return EINVAL; 86 | 87 | *strings = (char const *)malloc(section->sh_size); 88 | 89 | if (lseek(d, section->sh_offset, SEEK_SET) < 0) 90 | { 91 | free((void *)*strings); 92 | 93 | return errno; 94 | } 95 | 96 | if (read(d, (char *)*strings, section->sh_size) <= 0) 97 | { 98 | free((void *)*strings); 99 | 100 | return errno = EINVAL; 101 | } 102 | 103 | return 0; 104 | } 105 | //-------------------------------------------------------------------------------------------------- 106 | int read_symbol_table(int d, Elf_Shdr const *section, Elf_Sym **table) 107 | { 108 | if (NULL == section) 109 | return EINVAL; 110 | 111 | *table = (Elf_Sym *)malloc(section->sh_size); 112 | 113 | if (lseek(d, section->sh_offset, SEEK_SET) < 0) 114 | { 115 | free(*table); 116 | 117 | return errno; 118 | } 119 | 120 | if (read(d, *table, section->sh_size) <= 0) 121 | { 122 | free(*table); 123 | 124 | return errno = EINVAL; 125 | } 126 | 127 | return 0; 128 | } 129 | //-------------------------------------------------------------------------------------------------- 130 | int read_relocation_table(int d, Elf_Shdr const *section, Elf_Rel **table) 131 | { 132 | if (NULL == section) 133 | return EINVAL; 134 | 135 | *table = (Elf_Rel *)malloc(section->sh_size); 136 | 137 | if (lseek(d, section->sh_offset, SEEK_SET) < 0) 138 | { 139 | free(*table); 140 | 141 | return errno; 142 | } 143 | 144 | if (read(d, *table, section->sh_size) <= 0) 145 | { 146 | free(*table); 147 | 148 | return errno = EINVAL; 149 | } 150 | 151 | return 0; 152 | } 153 | //-------------------------------------------------------------------------------------------------- 154 | int section_by_index(int d, size_t const index, Elf_Shdr **section) 155 | { 156 | Elf_Ehdr *header = NULL; 157 | Elf_Shdr *sections = NULL; 158 | 159 | *section = NULL; 160 | 161 | if ( 162 | read_header(d, &header) || 163 | read_section_table(d, header, §ions) 164 | ) 165 | return errno; 166 | 167 | if (index < header->e_shnum) 168 | { 169 | *section = (Elf_Shdr *)malloc(sizeof(Elf_Shdr)); 170 | 171 | if (NULL == *section) 172 | { 173 | free(header); 174 | free(sections); 175 | 176 | return errno; 177 | } 178 | 179 | memcpy(*section, sections + index, sizeof(Elf_Shdr)); 180 | } 181 | else 182 | return EINVAL; 183 | 184 | free(header); 185 | free(sections); 186 | 187 | return 0; 188 | } 189 | //-------------------------------------------------------------------------------------------------- 190 | int section_by_type(int d, size_t const section_type, Elf_Shdr **section) 191 | { 192 | Elf_Ehdr *header = NULL; 193 | Elf_Shdr *sections = NULL; 194 | size_t i; 195 | 196 | *section = NULL; 197 | 198 | if ( 199 | read_header(d, &header) || 200 | read_section_table(d, header, §ions) 201 | ) 202 | return errno; 203 | 204 | for (i = 0; i < header->e_shnum; ++i) 205 | if (section_type == sections[i].sh_type) 206 | { 207 | *section = (Elf_Shdr *)malloc(sizeof(Elf_Shdr)); 208 | 209 | if (NULL == *section) 210 | { 211 | free(header); 212 | free(sections); 213 | 214 | return errno; 215 | } 216 | 217 | memcpy(*section, sections + i, sizeof(Elf_Shdr)); 218 | 219 | break; 220 | } 221 | 222 | free(header); 223 | free(sections); 224 | 225 | return 0; 226 | } 227 | //-------------------------------------------------------------------------------------------------- 228 | int section_by_name(int d, char const *section_name, Elf_Shdr **section) 229 | { 230 | Elf_Ehdr *header = NULL; 231 | Elf_Shdr *sections = NULL; 232 | char const *strings = NULL; 233 | size_t i; 234 | 235 | *section = NULL; 236 | 237 | if ( 238 | read_header(d, &header) || 239 | read_section_table(d, header, §ions) || 240 | read_string_table(d, §ions[header->e_shstrndx], &strings) 241 | ) 242 | return errno; 243 | 244 | for (i = 0; i < header->e_shnum; ++i) 245 | if (!strcmp(section_name, &strings[sections[i].sh_name])) 246 | { 247 | *section = (Elf_Shdr *)malloc(sizeof(Elf_Shdr)); 248 | 249 | if (NULL == *section) 250 | { 251 | free(header); 252 | free(sections); 253 | free((void *)strings); 254 | 255 | return errno; 256 | } 257 | 258 | memcpy(*section, sections + i, sizeof(Elf_Shdr)); 259 | 260 | break; 261 | } 262 | 263 | free(header); 264 | free(sections); 265 | free((void *)strings); 266 | 267 | return 0; 268 | } 269 | //-------------------------------------------------------------------------------------------------- 270 | int symbol_by_name(int d, Elf_Shdr *section, char const *name, Elf_Sym **symbol, size_t *index) 271 | { 272 | Elf_Shdr *strings_section = NULL; 273 | char const *strings = NULL; 274 | Elf_Sym *symbols = NULL; 275 | size_t i, amount; 276 | 277 | *symbol = NULL; 278 | *index = 0; 279 | 280 | if ( 281 | section_by_index(d, section->sh_link, &strings_section) || 282 | read_string_table(d, strings_section, &strings) || 283 | read_symbol_table(d, section, &symbols) 284 | ) 285 | return errno; 286 | 287 | amount = section->sh_size / sizeof(Elf_Sym); 288 | 289 | for (i = 0; i < amount; ++i) 290 | if (!strcmp(name, &strings[symbols[i].st_name])) 291 | { 292 | *symbol = (Elf_Sym *)malloc(sizeof(Elf_Sym)); 293 | 294 | if (NULL == *symbol) 295 | { 296 | free(strings_section); 297 | free((void *)strings); 298 | free(symbols); 299 | 300 | return errno; 301 | } 302 | 303 | memcpy(*symbol, symbols + i, sizeof(Elf_Sym)); 304 | *index = i; 305 | 306 | break; 307 | } 308 | 309 | free(strings_section); 310 | free((void *)strings); 311 | free(symbols); 312 | 313 | return 0; 314 | } 315 | //-------------------------------------------------------------------------------------------------- 316 | #ifdef __cplusplus 317 | extern "C" 318 | { 319 | #endif 320 | void *elf_hook(char const *module_filename, void const *module_address, char const *name, void const *substitution) 321 | { 322 | static size_t pagesize; 323 | 324 | int descriptor; //file descriptor of shared module 325 | 326 | Elf_Shdr 327 | *dynsym = NULL, // ".dynsym" section header 328 | *rel_plt = NULL, // ".rel.plt" section header 329 | *rel_dyn = NULL; // ".rel.dyn" section header 330 | 331 | Elf_Sym 332 | *symbol = NULL; //symbol table entry for symbol named "name" 333 | 334 | Elf_Rel 335 | *rel_plt_table = NULL, //array with ".rel.plt" entries 336 | *rel_dyn_table = NULL; //array with ".rel.dyn" entries 337 | 338 | size_t 339 | i, 340 | name_index, //index of symbol named "name" in ".dyn.sym" 341 | rel_plt_amount, // amount of ".rel.plt" entries 342 | rel_dyn_amount, // amount of ".rel.dyn" entries 343 | *name_address = NULL; //address of relocation for symbol named "name" 344 | 345 | void *original = NULL; //address of the symbol being substituted 346 | 347 | if (NULL == module_address || NULL == name || NULL == substitution) 348 | return original; 349 | 350 | if (!pagesize) 351 | pagesize = sysconf(_SC_PAGESIZE); 352 | 353 | descriptor = open(module_filename, O_RDONLY); 354 | 355 | if (descriptor < 0) 356 | return original; 357 | 358 | if ( 359 | section_by_type(descriptor, SHT_DYNSYM, &dynsym) || NULL == dynsym || //get ".dynsym" section 360 | symbol_by_name(descriptor, dynsym, name, &symbol, &name_index) || NULL == symbol || //actually, we need only the index of symbol named "name" in the ".dynsym" table 361 | section_by_name(descriptor, REL_PLT, &rel_plt) || NULL == rel_plt || //get ".rel.plt" (for 32-bit) or ".rela.plt" (for 64-bit) section 362 | section_by_name(descriptor, REL_DYN, &rel_dyn) || NULL == rel_dyn //get ".rel.dyn" (for 32-bit) or ".rela.dyn" (for 64-bit) section 363 | ) 364 | { //if something went wrong 365 | free(dynsym); 366 | free(rel_plt); 367 | free(rel_dyn); 368 | free(symbol); 369 | close(descriptor); 370 | 371 | return original; 372 | } 373 | //release the data used 374 | free(dynsym); 375 | free(symbol); 376 | 377 | rel_plt_table = (Elf_Rel *)(((size_t)module_address) + rel_plt->sh_addr); //init the ".rel.plt" array 378 | rel_plt_amount = rel_plt->sh_size / sizeof(Elf_Rel); //and get its size 379 | 380 | rel_dyn_table = (Elf_Rel *)(((size_t)module_address) + rel_dyn->sh_addr); //init the ".rel.dyn" array 381 | rel_dyn_amount = rel_dyn->sh_size / sizeof(Elf_Rel); //and get its size 382 | //release the data used 383 | free(rel_plt); 384 | free(rel_dyn); 385 | //and descriptor 386 | close(descriptor); 387 | //now we've got ".rel.plt" (needed for PIC) table and ".rel.dyn" (for non-PIC) table and the symbol's index 388 | for (i = 0; i < rel_plt_amount; ++i) //lookup the ".rel.plt" table 389 | if (ELF_R_SYM(rel_plt_table[i].r_info) == name_index) //if we found the symbol to substitute in ".rel.plt" 390 | { 391 | name_address = (size_t *)(((size_t)module_address) + rel_plt_table[i].r_offset); 392 | mprotect((void *)(((size_t)name_address) & (((size_t)-1) ^ (pagesize - 1))), pagesize, PROT_READ | PROT_WRITE); //mark a memory page that contains the relocation as writable 393 | original = (void *)*name_address; //save the original function address 394 | *name_address = (size_t)substitution; //and replace it with the substitutional 395 | 396 | 397 | break; //the target symbol appears in ".rel.plt" only once 398 | } 399 | 400 | if (original) 401 | return original; 402 | //we will get here only with 32-bit non-PIC module 403 | for (i = 0; i < rel_dyn_amount; ++i) //lookup the ".rel.dyn" table 404 | if (ELF_R_SYM(rel_dyn_table[i].r_info) == name_index) //if we found the symbol to substitute in ".rel.dyn" 405 | { 406 | name_address = (size_t *)(((size_t)module_address) + rel_dyn_table[i].r_offset); //get the relocation address (address of a relative CALL (0xE8) instruction's argument) 407 | 408 | if (!original) 409 | original = (void *)(*name_address + (size_t)name_address + sizeof(size_t)); //calculate an address of the original function by a relative CALL (0xE8) instruction's argument 410 | 411 | mprotect((void *)(((size_t)name_address) & (((size_t)-1) ^ (pagesize - 1))), pagesize, PROT_READ | PROT_WRITE); //mark a memory page that contains the relocation as writable 412 | 413 | if (errno) 414 | return NULL; 415 | 416 | *name_address = (size_t)substitution - (size_t)name_address - sizeof(size_t); //calculate a new relative CALL (0xE8) instruction's argument for the substitutional function and write it down 417 | 418 | mprotect((void *)(((size_t)name_address) & (((size_t)-1) ^ (pagesize - 1))), pagesize, PROT_READ | PROT_EXEC); //mark a memory page that contains the relocation back as executable 419 | 420 | if (errno) //if something went wrong 421 | { 422 | *name_address = (size_t)original - (size_t)name_address - sizeof(size_t); //then restore the original function address 423 | 424 | return NULL; 425 | } 426 | } 427 | 428 | return original; 429 | } 430 | #ifdef __cplusplus 431 | } 432 | #endif 433 | //================================================================================================== 434 | #endif 435 | --------------------------------------------------------------------------------