From patchwork Thu Jun 23 12:36:58 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 13322 X-Patchwork-Delegate: carlos@redhat.com Received: (qmail 62106 invoked by alias); 23 Jun 2016 12:37:12 -0000 Mailing-List: contact libc-alpha-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: libc-alpha-owner@sourceware.org Delivered-To: mailing list libc-alpha@sourceware.org Received: (qmail 62088 invoked by uid 89); 23 Jun 2016 12:37:12 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=0.6 required=5.0 tests=BAYES_40, RP_MATCHES_RCVD, SPF_HELO_PASS, UNSUBSCRIBE_BODY autolearn=no version=3.3.2 spammy=*free, HTo:D*ucla.edu, hexadecimal, oldstyle X-HELO: mx1.redhat.com Subject: Re: [PATCH] malloc: Remove malloc_get_state, malloc_set_state To: Paul Eggert References: <20160610093137.7306E400F0E5F@oldenburg.str.redhat.com> <575AFC0E.6000604@cs.ucla.edu> Cc: GNU C Library From: Florian Weimer Message-ID: <90d927bc-2056-2132-ef45-9ba5e5a94cd7@redhat.com> Date: Thu, 23 Jun 2016 14:36:58 +0200 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.1.1 MIME-Version: 1.0 In-Reply-To: <575AFC0E.6000604@cs.ucla.edu> On 06/10/2016 07:42 PM, Paul Eggert wrote: > Thanks for doing all this. Some comments: > >> +* The __malloc_get_state and __malloc_set_state functions have been >> removed >> + from the API. __malloc_get_state has been replaced with a stub >> + implementation. Existing undumped Emacs binaries will have to be >> + recompiled so that they do not use glibc malloc (or malloc heap >> dumping). >> + Existing installed Emacs binaries (after dumping) are not affected >> by this >> + change. > > The NEWS item should talk about the public API and so should refer to > names without leading underscores, and it'd be helpful to have a clearer > discussion about the backwards-compatibility constraints. Perhaps > wording like the following instead? > > ----- > The malloc_get_state and malloc_set_state functions have been removed. > Already-existing binaries that dynamically link to these functions will > get a hidden implementation in which malloc_get_state is a stub. As far > as we know, these functions are used only by GNU Emacs and this change > will not adversely affect already-built Emacs executables. Any undumped > Emacs executables, which normally exist only during an Emacs build, > should be rebuilt by re-running 'configure; make' in the Emacs build tree. > ----- Thanks, I've incorporated this into the attached patch. >> +attribute_compat_text_section >> +malloc_get_state (void) >> { >> ... >> + return NULL; >> } > > Perhaps __malloc_get_state should set errno to ENOSYS? Emacs won't care > about errno, so this would merely be insurance in case someone else does > care. Fixed. > Perhaps the test program should be retained and should check that the > hidden __malloc_get_state function indeed returns NULL? Dunno how you'd > test __malloc_set_state.... I have implemented a proper (obviously very white box) test. The test passes on x86_64 and ppc. It should be fairly realistic as far as such things go. We do not want to maintain a copy of the Emacs unexec mechanism inside glibc, so this is a good as it gets, I think. The test depends on the machinery for referencing compatibility symbols I posted earlier. Thanks, Florian malloc: Remove malloc_get_state, malloc_set_state After the removal of __malloc_initialize_hook, newly compiled Emacs binaries are no longer able to use these interfaces. malloc_get_state is only used during the Emacs build process, so we provide a stub implementation only. Existing Emacs binaries will not call this stub function, but still reference the symbol. The rewritten tst-mallocstate test constructs a dumped heap which should approximates what existing Emacs binaries pass to glibc malloc. 2016-06-23 Florian Weimer [BZ #19473] * malloc/malloc.h (malloc_get_state, malloc_set_state): Remove declarations. * malloc/malloc.c (malloc_get_state, malloc_set_state): Remove weak aliases. * malloc/hooks.c (__malloc_get_state): Remove definition. (malloc_get_state): New stub implementation as compatibility symbol. (malloc_set_state): Rename from __malloc_set_state. Turn into compat symbol. * malloc/tst-mallocstate.c: Rewrite to approximate how Emacs uses malloc_set_state. * malloc/Makefile (LDFLAGS-tst-mallocstate): Link with -rdynamic. diff --git a/NEWS b/NEWS index e2737d5..37c52b9 100644 --- a/NEWS +++ b/NEWS @@ -36,6 +36,14 @@ Version 2.24 * The deprecated __malloc_initialize_hook variable has been removed from the API. +* The malloc_get_state and malloc_set_state functions have been removed. + Already-existing binaries that dynamically link to these functions will + get a hidden implementation in which malloc_get_state is a stub. As far + as we know, these functions are used only by GNU Emacs and this change + will not adversely affect already-built Emacs executables. Any undumped + Emacs executables, which normally exist only during an Emacs build, should + be rebuilt by re-running “configure; make” in the Emacs build tree. + * The long unused localedef --old-style option has been removed. It hasn't done anything in over 16 years. Scripts using this option can safely drop it. diff --git a/malloc/Makefile b/malloc/Makefile index fa1730e..389da29 100644 --- a/malloc/Makefile +++ b/malloc/Makefile @@ -51,6 +51,7 @@ $(objpfx)tst-malloc-backtrace: $(shared-thread-library) $(objpfx)tst-malloc-thread-exit: $(shared-thread-library) $(objpfx)tst-malloc-thread-fail: $(shared-thread-library) $(objpfx)tst-malloc-fork-deadlock: $(shared-thread-library) +LDFLAGS-tst-mallocstate = -rdynamic # These should be removed by `make clean'. extra-objs = mcheck-init.o libmcheck.a diff --git a/malloc/hooks.c b/malloc/hooks.c index caa1e70..63c9e7a 100644 --- a/malloc/hooks.c +++ b/malloc/hooks.c @@ -447,6 +447,7 @@ memalign_check (size_t alignment, size_t bytes, const void *caller) return mem2mem_check (mem, bytes); } +#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_24) /* Get/set state: malloc_get_state() records the current state of all malloc variables (_except_ for the actual heap contents and `hook' @@ -492,60 +493,21 @@ struct malloc_save_state unsigned long narenas; }; +/* Dummy implementation which always fails. We need to provide this + symbol so that existing Emacs binaries continue to work with + BIND_NOW. */ void * -__malloc_get_state (void) +attribute_compat_text_section +malloc_get_state (void) { - struct malloc_save_state *ms; - int i; - mbinptr b; - - ms = (struct malloc_save_state *) __libc_malloc (sizeof (*ms)); - if (!ms) - return 0; - - (void) mutex_lock (&main_arena.mutex); - malloc_consolidate (&main_arena); - ms->magic = MALLOC_STATE_MAGIC; - ms->version = MALLOC_STATE_VERSION; - ms->av[0] = 0; - ms->av[1] = 0; /* used to be binblocks, now no longer used */ - ms->av[2] = top (&main_arena); - ms->av[3] = 0; /* used to be undefined */ - for (i = 1; i < NBINS; i++) - { - b = bin_at (&main_arena, i); - if (first (b) == b) - ms->av[2 * i + 2] = ms->av[2 * i + 3] = 0; /* empty bin */ - else - { - ms->av[2 * i + 2] = first (b); - ms->av[2 * i + 3] = last (b); - } - } - ms->sbrk_base = mp_.sbrk_base; - ms->sbrked_mem_bytes = main_arena.system_mem; - ms->trim_threshold = mp_.trim_threshold; - ms->top_pad = mp_.top_pad; - ms->n_mmaps_max = mp_.n_mmaps_max; - ms->mmap_threshold = mp_.mmap_threshold; - ms->check_action = check_action; - ms->max_sbrked_mem = main_arena.max_system_mem; - ms->max_total_mem = 0; - ms->n_mmaps = mp_.n_mmaps; - ms->max_n_mmaps = mp_.max_n_mmaps; - ms->mmapped_mem = mp_.mmapped_mem; - ms->max_mmapped_mem = mp_.max_mmapped_mem; - ms->using_malloc_checking = using_malloc_checking; - ms->max_fast = get_max_fast (); - ms->arena_test = mp_.arena_test; - ms->arena_max = mp_.arena_max; - ms->narenas = narenas; - (void) mutex_unlock (&main_arena.mutex); - return (void *) ms; + __set_errno (ENOSYS); + return NULL; } +compat_symbol (libc, malloc_get_state, malloc_get_state, GLIBC_2_0); int -__malloc_set_state (void *msptr) +attribute_compat_text_section +malloc_set_state (void *msptr) { struct malloc_save_state *ms = (struct malloc_save_state *) msptr; @@ -612,6 +574,9 @@ __malloc_set_state (void *msptr) return 0; } +compat_symbol (libc, malloc_set_state, malloc_set_state, GLIBC_2_0); + +#endif /* SHLIB_COMPAT */ /* * Local variables: diff --git a/malloc/malloc.c b/malloc/malloc.c index 1f5f166..7d21ef9 100644 --- a/malloc/malloc.c +++ b/malloc/malloc.c @@ -5271,8 +5271,6 @@ strong_alias (__libc_mallopt, __mallopt) weak_alias (__libc_mallopt, mallopt) weak_alias (__malloc_stats, malloc_stats) weak_alias (__malloc_usable_size, malloc_usable_size) weak_alias (__malloc_trim, malloc_trim) -weak_alias (__malloc_get_state, malloc_get_state) -weak_alias (__malloc_set_state, malloc_set_state) /* ------------------------------------------------------------ diff --git a/malloc/malloc.h b/malloc/malloc.h index 54b1862..e0c2788 100644 --- a/malloc/malloc.h +++ b/malloc/malloc.h @@ -134,13 +134,6 @@ extern void malloc_stats (void) __THROW; /* Output information about state of allocator to stream FP. */ extern int malloc_info (int __options, FILE *__fp) __THROW; -/* Record the state of all malloc variables in an opaque data structure. */ -extern void *malloc_get_state (void) __THROW; - -/* Restore the state of all malloc variables from data obtained with - malloc_get_state(). */ -extern int malloc_set_state (void *__ptr) __THROW; - /* Hooks for debugging and user-defined versions. */ extern void (*__MALLOC_HOOK_VOLATILE __free_hook) (void *__ptr, const void *) diff --git a/malloc/tst-mallocstate.c b/malloc/tst-mallocstate.c index a00d045..7e081c5 100644 --- a/malloc/tst-mallocstate.c +++ b/malloc/tst-mallocstate.c @@ -1,4 +1,5 @@ -/* Copyright (C) 2001-2016 Free Software Foundation, Inc. +/* Emulate Emacs heap dumping to test malloc_set_state. + Copyright (C) 2001-2016 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Wolfram Gloger , 2001. @@ -17,68 +18,488 @@ . */ #include +#include #include +#include +#include +#include + #include "malloc.h" -static int errors = 0; +/* Make the compatibility symbols availabile to this test case. */ +void *malloc_get_state (void); +compat_symbol_reference (libc, malloc_get_state, malloc_get_state, GLIBC_2_0); +int malloc_set_state (void *); +compat_symbol_reference (libc, malloc_set_state, malloc_set_state, GLIBC_2_0); +static int do_test (void); +#define TEST_FUNCTION do_test () +#include "../test-skeleton.c" + +/* Maximum object size in the fake heap. */ +enum { max_size = 64 }; + +/* Allocation actions. These are randomized actions executed on the + dumped heap (see allocation_tasks below). They are interspersed + with operations on the new heap (see heap_activity). */ +enum allocation_action + { + action_free, /* Dumped and freed. */ + action_realloc, /* Dumped and realloc'ed. */ + action_realloc_same, /* Dumped and realloc'ed, same size. */ + action_realloc_smaller, /* Dumped and realloc'ed, shrinked. */ + action_count + }; + +/* Dumped heap. Initialize it, so that the object is placed into the + .data section, for increased realism. The size is an upper bound; + we use about half of the space. */ +static size_t dumped_heap[action_count * max_size * max_size + / sizeof (size_t)] = {1}; + +/* Next free space in the dumped heap. Also top of the heap at the + end of the initialization procedure. */ +static size_t *next_heap_chunk; + +/* Copied from malloc.c and hooks.c. The version is deliberately + lower than the final version of malloc_set_state. */ +#define NBINS 128 +#define MALLOC_STATE_MAGIC 0x444c4541l +#define MALLOC_STATE_VERSION (0 * 0x100l + 4l) +static struct +{ + long magic; + long version; + void *av[NBINS * 2 + 2]; + char *sbrk_base; + int sbrked_mem_bytes; + unsigned long trim_threshold; + unsigned long top_pad; + unsigned int n_mmaps_max; + unsigned long mmap_threshold; + int check_action; + unsigned long max_sbrked_mem; + unsigned long max_total_mem; + unsigned int n_mmaps; + unsigned int max_n_mmaps; + unsigned long mmapped_mem; + unsigned long max_mmapped_mem; + int using_malloc_checking; + unsigned long max_fast; + unsigned long arena_test; + unsigned long arena_max; + unsigned long narenas; +} save_state = + { + .magic = MALLOC_STATE_MAGIC, + .version = MALLOC_STATE_VERSION, + }; + +/* Allocate a blob in the fake heap. */ +static void * +dumped_heap_alloc (size_t length) +{ + /* malloc needs three state bits in the size field, so the minimum + alignment is 8 even on 32-bit architectures. malloc_set_state + should be compatible with such heaps even if it currently + provides more alignment to applications. */ + enum + { + heap_alignment = 8, + heap_alignment_mask = heap_alignment - 1 + }; + _Static_assert (sizeof (size_t) <= heap_alignment, + "size_t compatible with heap alignment"); + + /* Need at least this many bytes for metadata and application + data. */ + size_t chunk_size = sizeof (size_t) + length; + /* Round up the allocation size to the heap alignment. */ + chunk_size += heap_alignment_mask; + chunk_size &= ~heap_alignment_mask; + if ((chunk_size & 3) != 0) + { + /* The lower three bits in the chunk size have to be 0. */ + write_message ("error: dumped_heap_alloc computed invalid chunk size\n"); + _exit (1); + } + if (next_heap_chunk == NULL) + /* Initialize the top of the heap. Add one word of zero padding, + to match existing practice. */ + { + dumped_heap[0] = 0; + next_heap_chunk = dumped_heap + 1; + } + else + /* The previous chunk is allocated. */ + chunk_size |= 1; + *next_heap_chunk = chunk_size; + + /* User data starts after the chunk header. */ + void *result = next_heap_chunk + 1; + next_heap_chunk += chunk_size / sizeof (size_t); + + /* Mark the previous chunk as used. */ + *next_heap_chunk = 1; + return result; +} + +/* Global seed variable for the random number generator. */ +static unsigned long long global_seed; + +/* Simple random number generator. The numbers are in the range from + 0 to UINT_MAX (inclusive). */ +static unsigned int +rand_next (unsigned long long *seed) +{ + /* Linear congruential generated as used for MMIX. */ + *seed = *seed * 6364136223846793005ULL + 1442695040888963407ULL; + return *seed >> 32; +} + +/* Fill LENGTH bytes at BUFFER with random contents, as determined by + SEED. */ +static void +randomize_buffer (unsigned char *buffer, size_t length, + unsigned long long seed) +{ + for (size_t i = 0; i < length; ++i) + buffer[i] = rand_next (&seed); +} + +/* Dumps the buffer to standard output, in hexadecimal. */ +static void +dump_hex (unsigned char *buffer, size_t length) +{ + for (int i = 0; i < length; ++i) + printf (" %02X", buffer[i]); +} + +/* Set to true if an error is encountered. */ +static bool errors = false; + +/* Keep track of object allocations. */ +struct allocation +{ + unsigned char *data; + unsigned int size; + unsigned int seed; +}; + +/* Check that the allocation task allocation has the expected + contents. */ +static void +check_allocation (const struct allocation *alloc, int index) +{ + size_t size = alloc->size; + if (alloc->data == NULL) + { + printf ("error: NULL pointer for allocation of size %zu at %d, seed %u\n", + size, index, alloc->seed); + errors = true; + return; + } + + unsigned char expected[4096]; + if (size > sizeof (expected)) + { + printf ("error: invalid allocation size %zu at %d, seed %u\n", + size, index, alloc->seed); + errors = true; + return; + } + randomize_buffer (expected, size, alloc->seed); + if (memcmp (alloc->data, expected, size) != 0) + { + printf ("error: allocation %d data mismatch, size %zu, seed %u\n", + index, size, alloc->seed); + printf (" expected:"); + dump_hex (expected, size); + putc ('\n', stdout); + printf (" actual:"); + dump_hex (alloc->data, size); + putc ('\n', stdout); + errors = true; + } +} + +/* A heap allocation combined with pending actions on it. */ +struct allocation_task +{ + struct allocation allocation; + enum allocation_action action; +}; + +/* Allocation tasks. Initialized by init_allocation_tasks and used by + perform_allocations. */ +enum { allocation_task_count = action_count * max_size }; +static struct allocation_task allocation_tasks[allocation_task_count]; + +/* Fisher-Yates shuffle of allocation_tasks. */ +static void +shuffle_allocation_tasks (void) +{ + for (int i = 0; i < allocation_task_count - 1; ++i) + { + /* Pick pair in the tail of the array. */ + int j = i + (rand_next (&global_seed) + % ((unsigned) (allocation_task_count - i))); + if (j < 0 || j >= allocation_task_count) + { + write_message ("error: test bug in shuffle\n"); + _exit (1); + } + /* Exchange. */ + struct allocation_task tmp = allocation_tasks[i]; + allocation_tasks[i] = allocation_tasks[j]; + allocation_tasks[j] = tmp; + } +} + +/* Set up the allocation tasks and the dumped heap. */ +static void +initial_allocations (void) +{ + /* Initialize in a position-dependent way. */ + for (int i = 0; i < allocation_task_count; ++i) + allocation_tasks[i] = (struct allocation_task) + { + .allocation = + { + .size = 1 + (i / action_count), + .seed = i, + }, + .action = i % action_count + }; + + /* Execute the tasks in a random order. */ + shuffle_allocation_tasks (); + + /* Initialize the contents of the dumped heap. */ + for (int i = 0; i < allocation_task_count; ++i) + { + struct allocation_task *task = allocation_tasks + i; + task->allocation.data = dumped_heap_alloc (task->allocation.size); + randomize_buffer (task->allocation.data, task->allocation.size, + task->allocation.seed); + } + + for (int i = 0; i < allocation_task_count; ++i) + check_allocation (&allocation_tasks[i].allocation, i); +} + +/* Indicates whether init_heap has run. This variable needs to be + volatile because malloc is declared __THROW, which implies it is a + leaf function, but we expect it to run our hooks. */ +static volatile bool heap_initialized; + +/* Executed by glibc malloc, through __malloc_initialize_hook + below. */ +static void +init_heap (void) +{ + write_message ("info: performing heap initialization\n"); + heap_initialized = true; + + /* Populate the dumped heap. */ + initial_allocations (); + + /* Complete initialization of the saved heap data structure. */ + save_state.sbrk_base = (void *) dumped_heap; + save_state.sbrked_mem_bytes = sizeof (dumped_heap); + /* Top pointer. Adjust so that it points to the start of struct + malloc_chunk. */ + save_state.av[2] = (void *) (next_heap_chunk - 1); + + /* Integrate the dumped heap into the process heap. */ + if (malloc_set_state (&save_state) != 0) + { + write_message ("error: malloc_set_state failed\n"); + _exit (1); + } +} + +/* Interpose the initialization callback. */ +void (*volatile __malloc_initialize_hook) (void) = init_heap; + +/* Simulate occasional unrelated heap activity in the non-dumped + heap. */ +enum { heap_activity_allocations_count = 32 }; +static struct allocation heap_activity_allocations + [heap_activity_allocations_count] = {}; +static int heap_activity_seed_counter = 1000 * 1000; + +static void +heap_activity (void) +{ + /* Only do this from time to time. */ + if ((rand_next (&global_seed) % 4) == 0) + { + int slot = rand_next (&global_seed) % heap_activity_allocations_count; + struct allocation *alloc = heap_activity_allocations + slot; + if (alloc->data == NULL) + { + alloc->size = rand_next (&global_seed) % (4096U + 1); + alloc->data = xmalloc (alloc->size); + alloc->seed = heap_activity_seed_counter++; + randomize_buffer (alloc->data, alloc->size, alloc->seed); + check_allocation (alloc, 1000 + slot); + } + else + { + check_allocation (alloc, 1000 + slot); + free (alloc->data); + alloc->data = NULL; + } + } +} + +static void +heap_activity_deallocate (void) +{ + for (int i = 0; i < heap_activity_allocations_count; ++i) + free (heap_activity_allocations[i].data); +} + +/* Perform a full heap check across the dumped heap allocation tasks, + and the simulated heap activity directly above. */ +static void +full_heap_check (void) +{ + /* Dumped heap. */ + for (int i = 0; i < allocation_task_count; ++i) + if (allocation_tasks[i].allocation.data != NULL) + check_allocation (&allocation_tasks[i].allocation, i); + + /* Heap activity allocations. */ + for (int i = 0; i < heap_activity_allocations_count; ++i) + if (heap_activity_allocations[i].data != NULL) + check_allocation (heap_activity_allocations + i, i); +} + +/* Used as an optimization barrier to force a heap allocation. */ +__attribute__ ((noinline, noclone)) static void -merror (const char *msg) +my_free (void *ptr) { - ++errors; - printf ("Error: %s\n", msg); + free (ptr); } static int do_test (void) { - void *p1, *p2; - void *save_state; - long i; + my_free (malloc (1)); + if (!heap_initialized) + { + printf ("error: heap was not initialized by malloc\n"); + return 1; + } + /* The first pass performs the randomly generated allocation + tasks. */ + write_message ("info: first pass through allocation tasks\n"); + full_heap_check (); + + /* Execute the post-undump tasks in a random order. */ + shuffle_allocation_tasks (); + + for (int i = 0; i < allocation_task_count; ++i) + { + heap_activity (); + struct allocation_task *task = allocation_tasks + i; + switch (task->action) + { + case action_free: + check_allocation (&task->allocation, i); + free (task->allocation.data); + task->allocation.data = NULL; + break; + + case action_realloc: + check_allocation (&task->allocation, i); + task->allocation.data = xrealloc + (task->allocation.data, task->allocation.size + max_size); + check_allocation (&task->allocation, i); + break; + + case action_realloc_same: + check_allocation (&task->allocation, i); + task->allocation.data = xrealloc + (task->allocation.data, task->allocation.size); + check_allocation (&task->allocation, i); + break; + + case action_realloc_smaller: + check_allocation (&task->allocation, i); + size_t new_size = task->allocation.size - 1; + task->allocation.data = xrealloc (task->allocation.data, new_size); + if (new_size == 0) + { + if (task->allocation.data != NULL) + { + printf ("error: realloc with size zero did not deallocate\n"); + errors = true; + } + /* No further action on this task. */ + task->action = action_free; + } + else + { + task->allocation.size = new_size; + check_allocation (&task->allocation, i); + } + break; + + case action_count: + abort (); + } + full_heap_check (); + } + + /* The second pass frees the objects which were allocated during the + first pass. */ + write_message ("info: second pass through allocation tasks\n"); + + shuffle_allocation_tasks (); + for (int i = 0; i < allocation_task_count; ++i) + { + heap_activity (); + struct allocation_task *task = allocation_tasks + i; + switch (task->action) + { + case action_free: + /* Already freed, nothing to do. */ + break; + + case action_realloc: + case action_realloc_same: + case action_realloc_smaller: + check_allocation (&task->allocation, i); + free (task->allocation.data); + task->allocation.data = NULL; + break; + + case action_count: + abort (); + } + full_heap_check (); + } + + heap_activity_deallocate (); + + /* Check that the malloc_get_state stub behaves in the intended + way. */ errno = 0; - - p1 = malloc (10); - if (p1 == NULL) - merror ("malloc (10) failed."); - - p2 = malloc (20); - if (p2 == NULL) - merror ("malloc (20) failed."); - - free (malloc (10)); - - for (i = 0; i < 100; ++i) + if (malloc_get_state () != NULL) { - save_state = malloc_get_state (); - if (save_state == NULL) - { - merror ("malloc_get_state () failed."); - break; - } - /*free (malloc (10)); This could change the top chunk! */ - malloc_set_state (save_state); - p1 = realloc (p1, i * 4 + 4); - if (p1 == NULL) - merror ("realloc (i*4) failed."); - free (save_state); + printf ("error: malloc_get_state succeeded\n"); + errors = true; + } + if (errno != ENOSYS) + { + printf ("error: malloc_get_state: %m\n"); + errors = true; } - p1 = realloc (p1, 40); - free (p2); - p2 = malloc (10); - if (p2 == NULL) - merror ("malloc (10) failed."); - free (p1); - - return errors != 0; + return errors; } - -/* - * Local variables: - * c-basic-offset: 2 - * End: - */ - -#define TEST_FUNCTION do_test () -#include "../test-skeleton.c"