| /* Minimal malloc implementation for interposition tests. |
| Copyright (C) 2016-2018 Free Software Foundation, Inc. |
| This file is part of the GNU C Library. |
| |
| The GNU C Library is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Lesser General Public License as |
| published by the Free Software Foundation; either version 2.1 of the |
| License, or (at your option) any later version. |
| |
| The GNU C Library is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public |
| License along with the GNU C Library; see the file COPYING.LIB. If |
| not, see <http://www.gnu.org/licenses/>. */ |
| |
| #include "tst-interpose-aux.h" |
| |
| #include <errno.h> |
| #include <stdarg.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/mman.h> |
| #include <sys/uio.h> |
| #include <unistd.h> |
| |
| #if INTERPOSE_THREADS |
| #include <pthread.h> |
| #endif |
| |
| /* Print the error message and terminate the process with status 1. */ |
| __attribute__ ((noreturn)) |
| __attribute__ ((format (printf, 1, 2))) |
| static void * |
| fail (const char *format, ...) |
| { |
| /* This assumes that vsnprintf will not call malloc. It does not do |
| so for the format strings we use. */ |
| char message[4096]; |
| va_list ap; |
| va_start (ap, format); |
| vsnprintf (message, sizeof (message), format, ap); |
| va_end (ap); |
| |
| enum { count = 3 }; |
| struct iovec iov[count]; |
| |
| iov[0].iov_base = (char *) "error: "; |
| iov[1].iov_base = (char *) message; |
| iov[2].iov_base = (char *) "\n"; |
| |
| for (int i = 0; i < count; ++i) |
| iov[i].iov_len = strlen (iov[i].iov_base); |
| |
| int unused __attribute__ ((unused)); |
| unused = writev (STDOUT_FILENO, iov, count); |
| _exit (1); |
| } |
| |
| #if INTERPOSE_THREADS |
| static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; |
| #endif |
| |
| static void |
| lock (void) |
| { |
| #if INTERPOSE_THREADS |
| int ret = pthread_mutex_lock (&mutex); |
| if (ret != 0) |
| { |
| errno = ret; |
| fail ("pthread_mutex_lock: %m"); |
| } |
| #endif |
| } |
| |
| static void |
| unlock (void) |
| { |
| #if INTERPOSE_THREADS |
| int ret = pthread_mutex_unlock (&mutex); |
| if (ret != 0) |
| { |
| errno = ret; |
| fail ("pthread_mutex_unlock: %m"); |
| } |
| #endif |
| } |
| |
| struct __attribute__ ((aligned (__alignof__ (max_align_t)))) allocation_header |
| { |
| size_t allocation_index; |
| size_t allocation_size; |
| }; |
| |
| /* Array of known allocations, to track invalid frees. */ |
| enum { max_allocations = 65536 }; |
| static struct allocation_header *allocations[max_allocations]; |
| static size_t allocation_index; |
| static size_t deallocation_count; |
| |
| /* Sanity check for successful malloc interposition. */ |
| __attribute__ ((destructor)) |
| static void |
| check_for_allocations (void) |
| { |
| if (allocation_index == 0) |
| { |
| /* Make sure that malloc is called at least once from libc. */ |
| void *volatile ptr = strdup ("ptr"); |
| /* Compiler barrier. The strdup function calls malloc, which |
| updates allocation_index, but strdup is marked __THROW, so |
| the compiler could optimize away the reload. */ |
| __asm__ volatile ("" ::: "memory"); |
| free (ptr); |
| /* If the allocation count is still zero, it means we did not |
| interpose malloc successfully. */ |
| if (allocation_index == 0) |
| fail ("malloc does not seem to have been interposed"); |
| } |
| } |
| |
| static struct allocation_header *get_header (const char *op, void *ptr) |
| { |
| struct allocation_header *header = ((struct allocation_header *) ptr) - 1; |
| if (header->allocation_index >= allocation_index) |
| fail ("%s: %p: invalid allocation index: %zu (not less than %zu)", |
| op, ptr, header->allocation_index, allocation_index); |
| if (allocations[header->allocation_index] != header) |
| fail ("%s: %p: allocation pointer does not point to header, but %p", |
| op, ptr, allocations[header->allocation_index]); |
| return header; |
| } |
| |
| /* Internal helper functions. Those must be called while the lock is |
| acquired. */ |
| |
| static void * |
| malloc_internal (size_t size) |
| { |
| if (allocation_index == max_allocations) |
| { |
| errno = ENOMEM; |
| return NULL; |
| } |
| size_t allocation_size = size + sizeof (struct allocation_header); |
| if (allocation_size < size) |
| { |
| errno = ENOMEM; |
| return NULL; |
| } |
| |
| size_t index = allocation_index++; |
| void *result = mmap (NULL, allocation_size, PROT_READ | PROT_WRITE, |
| MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
| if (result == MAP_FAILED) |
| return NULL; |
| allocations[index] = result; |
| *allocations[index] = (struct allocation_header) |
| { |
| .allocation_index = index, |
| .allocation_size = allocation_size |
| }; |
| return allocations[index] + 1; |
| } |
| |
| static void |
| free_internal (const char *op, struct allocation_header *header) |
| { |
| size_t index = header->allocation_index; |
| int result = mprotect (header, header->allocation_size, PROT_NONE); |
| if (result != 0) |
| fail ("%s: mprotect (%p, %zu): %m", op, header, header->allocation_size); |
| /* Catch double-free issues. */ |
| allocations[index] = NULL; |
| ++deallocation_count; |
| } |
| |
| static void * |
| realloc_internal (void *ptr, size_t new_size) |
| { |
| struct allocation_header *header = get_header ("realloc", ptr); |
| size_t old_size = header->allocation_size - sizeof (struct allocation_header); |
| if (old_size >= new_size) |
| return ptr; |
| |
| void *newptr = malloc_internal (new_size); |
| if (newptr == NULL) |
| return NULL; |
| memcpy (newptr, ptr, old_size); |
| free_internal ("realloc", header); |
| return newptr; |
| } |
| |
| /* Public interfaces. These functions must perform locking. */ |
| |
| size_t |
| malloc_allocation_count (void) |
| { |
| lock (); |
| size_t count = allocation_index; |
| unlock (); |
| return count; |
| } |
| |
| size_t |
| malloc_deallocation_count (void) |
| { |
| lock (); |
| size_t count = deallocation_count; |
| unlock (); |
| return count; |
| } |
| void * |
| malloc (size_t size) |
| { |
| lock (); |
| void *result = malloc_internal (size); |
| unlock (); |
| return result; |
| } |
| |
| void |
| free (void *ptr) |
| { |
| if (ptr == NULL) |
| return; |
| lock (); |
| struct allocation_header *header = get_header ("free", ptr); |
| free_internal ("free", header); |
| unlock (); |
| } |
| |
| void * |
| calloc (size_t a, size_t b) |
| { |
| if (b > 0 && a > SIZE_MAX / b) |
| { |
| errno = ENOMEM; |
| return NULL; |
| } |
| lock (); |
| /* malloc_internal uses mmap, so the memory is zeroed. */ |
| void *result = malloc_internal (a * b); |
| unlock (); |
| return result; |
| } |
| |
| void * |
| realloc (void *ptr, size_t n) |
| { |
| if (n ==0) |
| { |
| free (ptr); |
| return NULL; |
| } |
| else if (ptr == NULL) |
| return malloc (n); |
| else |
| { |
| lock (); |
| void *result = realloc_internal (ptr, n); |
| unlock (); |
| return result; |
| } |
| } |