runtime/dyninst/shm.c - systemtap

Global variables defined

Functions defined

Macros defined

Source code

/* -*- linux-c -*-
* Shared Memory Functions
* Copyright (C) 2012 Red Hat Inc.
*
* This file is part of systemtap, and is free software.  You can
* redistribute it and/or modify it under the terms of the GNU General
* Public License (GPL); either version 2, or (at your option) any
* later version.
*/

#ifndef _STAPDYN_SHM_H_
#define _STAPDYN_SHM_H_

#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

static char _stp_shm_name[NAME_MAX] = { '\0' };
static int _stp_shm_fd = -1;
static int _stp_shm_page_size = -1;
static off_t _stp_shm_size = 0;
static off_t _stp_shm_allocated = 0;
static void *_stp_shm_base = NULL;

static const char *_stp_shm_init(void);
static int _stp_shm_connect(const char *name);
static void *_stp_shm_alloc(size_t size);
static void *_stp_shm_zalloc(size_t size);
static void _stp_shm_free(void *ptr);
static void _stp_shm_finalize(void);
static void _stp_shm_destroy(void);

#ifdef DEBUG_SHM
#define shm_dbug(fmt, args...) _stp_dbug(__FUNCTION__, __LINE__, fmt, ##args)
#else
#define shm_dbug(fmt, args...)
#endif

// Create and initialize the shared memory for this module.
static const char *_stp_shm_init(void)
{
    char name[NAME_MAX];
    long page_size;
    int fd;
    void *base;

    // If we already have shared memory, carry on...
    if (_stp_shm_base)
        return _stp_shm_name;

    // Find the increment to use in growing our memory size.  Since this is
    // used in mmap, only multiples of the page size make sense.
    page_size = sysconf(_SC_PAGESIZE);
    if (page_size < 1)
        return NULL;

    // Create a unique name for our shared memory.  It need not be anything
        // secret because shm_open's flags will ensure security.
        (void) snprintf(name, NAME_MAX, "/stapdyn.%d", getpid());

    // Create the actual share memory.  The O_EXCL saves us from the
    // possible mktemp race.  Only USR file mode bits are added, because
    // that's all that ptrace will allow to attach anyway.  (Unless you're
    // root, but that deserves much larger consideration.)
    fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
    if (fd < 0)
        return NULL;

    // Start the shared memory sized with just one unit.
    if (ftruncate(fd, page_size) < 0)
        goto err_fd;

    // Now finally map it into this process.
    base = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
            MAP_SHARED, fd, 0);
    if (base == MAP_FAILED)
        goto err_fd;

    // We're done; set globals and go home!
    strlcpy(_stp_shm_name, name, sizeof(_stp_shm_name));
    _stp_shm_fd = fd;
    _stp_shm_page_size = page_size;
    _stp_shm_size = page_size;
    _stp_shm_allocated = 0;
    _stp_shm_base = base;
    shm_dbug("initialized %s @ %p", _stp_shm_name, _stp_shm_base);
    return _stp_shm_name;

err_fd:
    close(fd);
    shm_unlink(_stp_shm_name);
    return NULL;
}

static int _stp_shm_connect(const char *name)
{
    int rc = 0, fd = -1;
    struct stat st;
    void *base;

    // NB: since we're just connecting, we won't set _stp_shm_name,
    // so _stp_shm_destroy won't try to unlink it from this process.

    // Open the pre-existing shared memory
    fd = shm_open(name, O_RDWR, 0);
    if (fd < 0)
        rc = fd;

    // Find out the shared memory's size.
    // (This implies that the main process is done allocating.)
    if (rc == 0)
        rc = fstat(fd, &st);

    // Map it into this process.
    if (rc == 0) {
        base = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE,
                MAP_SHARED, fd, 0);
        if (base == MAP_FAILED)
            rc = -1;
        else
            _stp_shm_base = base;
    }

    if (fd >= 0)
        close(fd);

    if (rc == 0)
        shm_dbug("connected pid %d to %s @ %p", getpid(), name, _stp_shm_base);
    return rc;
}

// Allocate space from shared memory
static void *_stp_shm_alloc(size_t size)
{
    void *alloc;

    // We're not initialized or already finalized.
    // Either way, we're not taking new requests.
    if (_stp_shm_fd < 0)
        return NULL;

    // Round up to 8-byte aligned sizes, just to be a little safer
    size = (size + 7) & ~7;
    if (size <= 0)
        return NULL; // either 0 requested or overflow

    // Check if more memory is needed.
    if (_stp_shm_size - _stp_shm_allocated < size) {
        void *new_base;

        // Round up to the nearest page size
        off_t new_size = _stp_shm_allocated + size;
        new_size += _stp_shm_page_size - 1;
        new_size /= _stp_shm_page_size;
        new_size *= _stp_shm_page_size;
        if (new_size < _stp_shm_allocated ||
            new_size - _stp_shm_allocated < size)
            return NULL; // math overflow?

        // Try to resize the underlying file.
        if (ftruncate(_stp_shm_fd, new_size) < 0)
            return NULL;

        // Try to remap the address in memory.
        new_base = mremap(_stp_shm_base, _stp_shm_size,
                  new_size, MREMAP_MAYMOVE);
        if (new_base == MAP_FAILED)
            return NULL;

        // Update globals
        _stp_shm_size = new_size;
        _stp_shm_base = new_base;
    }

    // Finally return some memory.
    alloc = _stp_shm_base + _stp_shm_allocated;
    _stp_shm_allocated += size;
    return alloc;
}

// Allocate zeroed space from shared memory
static void *_stp_shm_zalloc(size_t size)
{
    void *ptr = _stp_shm_alloc(size);
    if (ptr)
        memset(ptr, 0, size);
    return ptr;
}

static void _stp_shm_free(void *ptr __attribute__((unused)))
{
    // NO-OP; we don't try to reclaim individual pieces.
}

// Signal that we're done allocating in shared memory.
// It won't be allowed to grow or move from here on out.
static void _stp_shm_finalize(void)
{
    if (_stp_shm_fd >= 0) {
        close(_stp_shm_fd);
        _stp_shm_fd = -1;
    }
    shm_dbug("mapped %" PRIi64 " bytes @ %p, used %" PRIi64, _stp_shm_size,
         _stp_shm_base, _stp_shm_allocated);
}

// Tear down everything we created... *sniff*
// NB: Make sure not to reference any memory within after this!
// (Other processes may still have their own mmap reference though.)
// Don't destroy things in shm that may still be used by other processes!
static void _stp_shm_destroy(void)
{
    if (_stp_shm_base) {
        munmap(_stp_shm_base, _stp_shm_size);
        _stp_shm_base = NULL;
        _stp_shm_size = 0;
        _stp_shm_allocated = 0;
    }

    if (_stp_shm_fd >= 0) {
        close(_stp_shm_fd);
        _stp_shm_fd = -1;
    }

    if (_stp_shm_name[0]) {
        shm_unlink(_stp_shm_name);
        _stp_shm_name[0] = '\0';
    }
}

#endif /* _STAPDYN_SHM_H_ */