summaryrefslogtreecommitdiff
path: root/c/malloc_closure.h
blob: bebb93dc15595ccc0785d3bcb79520363f0101a9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/*
 * This file is from CPython's Modules/_ctypes/malloc_closure.c
 * and has received some edits.
 */

#include <ffi.h>
#ifdef MS_WIN32
#include <windows.h>
#else
#include <sys/mman.h>
#include <unistd.h>
# if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
#  define MAP_ANONYMOUS MAP_ANON
# endif
#endif

/* On PaX enable kernels that have MPROTECT enable we can't use PROT_EXEC.

   This is, apparently, an undocumented change to ffi_prep_closure():
   depending on the Linux kernel we're running on, we must give it a
   mmap that is either PROT_READ|PROT_WRITE|PROT_EXEC or only
   PROT_READ|PROT_WRITE.  In the latter case, just trying to obtain a
   mmap with PROT_READ|PROT_WRITE|PROT_EXEC would kill our process(!),
   but in that situation libffi is fine with only PROT_READ|PROT_WRITE.
   There is nothing in the libffi API to know that, though, so we have
   to guess by parsing /proc/self/status.  "Meh."
 */
#ifdef __linux__
#include <stdlib.h>

static int emutramp_enabled = -1;

static int
emutramp_enabled_check (void)
{
    char *buf = NULL;
    size_t len = 0;
    FILE *f;
    int ret;
    f = fopen ("/proc/self/status", "r");
    if (f == NULL)
        return 0;
    ret = 0;

    while (getline (&buf, &len, f) != -1)
        if (!strncmp (buf, "PaX:", 4))
            {
                char emutramp;
                if (sscanf (buf, "%*s %*c%c", &emutramp) == 1)
                    ret = (emutramp == 'E');
                break;
            }
    free (buf);
    fclose (f);
    return ret;
}

#define is_emutramp_enabled() (emutramp_enabled >= 0 ? emutramp_enabled \
        : (emutramp_enabled = emutramp_enabled_check ()))
#else
#define is_emutramp_enabled() 0
#endif


/* 'allocate_num_pages' is dynamically adjusted starting from one
   page.  It grows by a factor of PAGE_ALLOCATION_GROWTH_RATE.  This is
   meant to handle both the common case of not needing a lot of pages,
   and the rare case of needing many of them.  Systems in general have a
   limit of how many mmap'd blocks can be open.
*/

#define PAGE_ALLOCATION_GROWTH_RATE  1.3

static Py_ssize_t allocate_num_pages = 0;

/* #define MALLOC_CLOSURE_DEBUG */ /* enable for some debugging output */

/******************************************************************/

union mmaped_block {
    ffi_closure closure;
    union mmaped_block *next;
};

static union mmaped_block *free_list = 0;
static Py_ssize_t _pagesize = 0;

static void more_core(void)
{
    union mmaped_block *item;
    Py_ssize_t count, i;

/* determine the pagesize */
#ifdef MS_WIN32
    if (!_pagesize) {
        SYSTEM_INFO systeminfo;
        GetSystemInfo(&systeminfo);
        _pagesize = systeminfo.dwPageSize;
    }
#else
    if (!_pagesize) {
#ifdef _SC_PAGESIZE
        _pagesize = sysconf(_SC_PAGESIZE);
#else
        _pagesize = getpagesize();
#endif
    }
#endif
    if (_pagesize <= 0)
        _pagesize = 4096;

    /* bump 'allocate_num_pages' */
    allocate_num_pages = 1 + (
        (Py_ssize_t)(allocate_num_pages * PAGE_ALLOCATION_GROWTH_RATE));

    /* calculate the number of mmaped_blocks to allocate */
    count = (allocate_num_pages * _pagesize) / sizeof(union mmaped_block);

    /* allocate a memory block */
#ifdef MS_WIN32
    item = (union mmaped_block *)VirtualAlloc(NULL,
                                           count * sizeof(union mmaped_block),
                                           MEM_COMMIT,
                                           PAGE_EXECUTE_READWRITE);
    if (item == NULL)
        return;
#else
    {
    int prot = PROT_READ | PROT_WRITE | PROT_EXEC;
    if (is_emutramp_enabled ())
        prot &= ~PROT_EXEC;
    item = (union mmaped_block *)mmap(NULL,
                        allocate_num_pages * _pagesize,
                        prot,
                        MAP_PRIVATE | MAP_ANONYMOUS,
                        -1,
                        0);
    if (item == (void *)MAP_FAILED)
        return;
    }
#endif

#ifdef MALLOC_CLOSURE_DEBUG
    printf("block at %p allocated (%ld bytes), %ld mmaped_blocks\n",
           item, (long)(allocate_num_pages * _pagesize), (long)count);
#endif
    /* put them into the free list */
    for (i = 0; i < count; ++i) {
        item->next = free_list;
        free_list = item;
        ++item;
    }
}

/******************************************************************/

/* put the item back into the free list */
static void cffi_closure_free(ffi_closure *p)
{
    union mmaped_block *item = (union mmaped_block *)p;
    item->next = free_list;
    free_list = item;
}

/* return one item from the free list, allocating more if needed */
static ffi_closure *cffi_closure_alloc(void)
{
    union mmaped_block *item;
    if (!free_list)
        more_core();
    if (!free_list)
        return NULL;
    item = free_list;
    free_list = item->next;
    return &item->closure;
}