aboutsummaryrefslogtreecommitdiff
path: root/malloc
diff options
context:
space:
mode:
Diffstat (limited to 'malloc')
-rw-r--r--malloc/Makefile29
-rw-r--r--malloc/arena.c62
-rw-r--r--malloc/hooks.c95
-rw-r--r--malloc/malloc.c400
-rw-r--r--malloc/malloc.h7
-rw-r--r--malloc/tst-interpose-aux-nothread.c20
-rw-r--r--malloc/tst-interpose-aux-thread.c20
-rw-r--r--malloc/tst-interpose-aux.c270
-rw-r--r--malloc/tst-interpose-aux.h30
-rw-r--r--malloc/tst-interpose-nothread.c20
-rw-r--r--malloc/tst-interpose-skeleton.c204
-rw-r--r--malloc/tst-interpose-static-nothread.c19
-rw-r--r--malloc/tst-interpose-static-thread.c19
-rw-r--r--malloc/tst-interpose-thread.c20
-rw-r--r--malloc/tst-mallocstate.c505
15 files changed, 1421 insertions, 299 deletions
diff --git a/malloc/Makefile b/malloc/Makefile
index 761a976fa3..f34c2a75ba 100644
--- a/malloc/Makefile
+++ b/malloc/Makefile
@@ -30,7 +30,15 @@ tests := mallocbug tst-malloc tst-valloc tst-calloc tst-obstack \
tst-pvalloc tst-memalign tst-mallopt tst-scratch_buffer \
tst-malloc-backtrace tst-malloc-thread-exit \
tst-malloc-thread-fail tst-malloc-fork-deadlock \
- tst-mallocfork2
+ tst-mallocfork2 \
+ tst-interpose-nothread \
+ tst-interpose-thread \
+
+tests-static := \
+ tst-interpose-static-nothread \
+ tst-interpose-static-thread \
+
+tests += $(tests-static)
test-srcs = tst-mtrace
routines = malloc morecore mcheck mtrace obstack \
@@ -44,6 +52,15 @@ non-lib.a := libmcheck.a
extra-libs = libmemusage libmtracectl
extra-libs-others = $(extra-libs)
+# Helper objects for some tests.
+extra-tests-objs += \
+ tst-interpose-aux-nothread.o \
+ tst-interpose-aux-thread.o \
+
+test-extras = \
+ tst-interpose-aux-nothread \
+ tst-interpose-aux-thread \
+
libmemusage-routines = memusage
libmemusage-inhibit-o = $(filter-out .os,$(object-suffixes))
@@ -71,6 +88,9 @@ $(objpfx)tst-malloc-thread-exit: $(shared-thread-library)
$(objpfx)tst-malloc-thread-fail: $(shared-thread-library)
$(objpfx)tst-malloc-fork-deadlock: $(shared-thread-library)
+# Export the __malloc_initialize_hook variable to libc.so.
+LDFLAGS-tst-mallocstate = -rdynamic
+
# These should be removed by `make clean'.
extra-objs = mcheck-init.o libmcheck.a
@@ -189,3 +209,10 @@ $(foreach o,$(all-object-suffixes),$(objpfx)malloc$(o)): arena.c hooks.c
# Compile the tests with a flag which suppresses the mallopt call in
# the test skeleton.
$(tests:%=$(objpfx)%.o): CPPFLAGS += -DTEST_NO_MALLOPT
+
+$(objpfx)tst-interpose-nothread: $(objpfx)tst-interpose-aux-nothread.o
+$(objpfx)tst-interpose-thread: \
+ $(objpfx)tst-interpose-aux-thread.o $(shared-thread-library)
+$(objpfx)tst-interpose-static-nothread: $(objpfx)tst-interpose-aux-nothread.o
+$(objpfx)tst-interpose-static-thread: \
+ $(objpfx)tst-interpose-aux-thread.o $(static-thread-library)
diff --git a/malloc/arena.c b/malloc/arena.c
index 6c23c8e107..ada1a7245e 100644
--- a/malloc/arena.c
+++ b/malloc/arena.c
@@ -73,7 +73,7 @@ static __thread mstate thread_arena attribute_tls_model_ie;
members of struct malloc_state objects. No other locks must be
acquired after free_list_lock has been acquired. */
-static mutex_t free_list_lock = _LIBC_LOCK_INITIALIZER;
+__libc_lock_define_initialized (static, free_list_lock);
static size_t narenas = 1;
static mstate free_list;
@@ -89,7 +89,7 @@ static mstate free_list;
acquired, no arena lock must have been acquired, but it is
permitted to acquire arena locks subsequently, while list_lock is
acquired. */
-static mutex_t list_lock = _LIBC_LOCK_INITIALIZER;
+__libc_lock_define_initialized (static, list_lock);
/* Already initialized? */
int __malloc_initialized = -1;
@@ -112,7 +112,7 @@ int __malloc_initialized = -1;
#define arena_lock(ptr, size) do { \
if (ptr && !arena_is_corrupt (ptr)) \
- (void) mutex_lock (&ptr->mutex); \
+ __libc_lock_lock (ptr->mutex); \
else \
ptr = arena_get2 ((size), NULL); \
} while (0)
@@ -122,7 +122,7 @@ int __malloc_initialized = -1;
#define heap_for_ptr(ptr) \
((heap_info *) ((unsigned long) (ptr) & ~(HEAP_MAX_SIZE - 1)))
#define arena_for_chunk(ptr) \
- (chunk_non_main_arena (ptr) ? heap_for_ptr (ptr)->ar_ptr : &main_arena)
+ (chunk_main_arena (ptr) ? &main_arena : heap_for_ptr (ptr)->ar_ptr)
/**************************************************************************/
@@ -145,11 +145,11 @@ __malloc_fork_lock_parent (void)
/* We do not acquire free_list_lock here because we completely
reconstruct free_list in __malloc_fork_unlock_child. */
- (void) mutex_lock (&list_lock);
+ __libc_lock_lock (list_lock);
for (mstate ar_ptr = &main_arena;; )
{
- (void) mutex_lock (&ar_ptr->mutex);
+ __libc_lock_lock (ar_ptr->mutex);
ar_ptr = ar_ptr->next;
if (ar_ptr == &main_arena)
break;
@@ -165,12 +165,12 @@ __malloc_fork_unlock_parent (void)
for (mstate ar_ptr = &main_arena;; )
{
- (void) mutex_unlock (&ar_ptr->mutex);
+ __libc_lock_unlock (ar_ptr->mutex);
ar_ptr = ar_ptr->next;
if (ar_ptr == &main_arena)
break;
}
- (void) mutex_unlock (&list_lock);
+ __libc_lock_unlock (list_lock);
}
void
@@ -182,13 +182,13 @@ __malloc_fork_unlock_child (void)
/* Push all arenas to the free list, except thread_arena, which is
attached to the current thread. */
- mutex_init (&free_list_lock);
+ __libc_lock_init (free_list_lock);
if (thread_arena != NULL)
thread_arena->attached_threads = 1;
free_list = NULL;
for (mstate ar_ptr = &main_arena;; )
{
- mutex_init (&ar_ptr->mutex);
+ __libc_lock_init (ar_ptr->mutex);
if (ar_ptr != thread_arena)
{
/* This arena is no longer attached to any thread. */
@@ -201,7 +201,7 @@ __malloc_fork_unlock_child (void)
break;
}
- mutex_init (&list_lock);
+ __libc_lock_init (list_lock);
}
/* Initialization routine. */
@@ -574,12 +574,12 @@ heap_trim (heap_info *heap, size_t pad)
/* fencepost must be properly aligned. */
misalign = ((long) p) & MALLOC_ALIGN_MASK;
p = chunk_at_offset (prev_heap, prev_size - misalign);
- assert (p->size == (0 | PREV_INUSE)); /* must be fencepost */
+ assert (chunksize_nomask (p) == (0 | PREV_INUSE)); /* must be fencepost */
p = prev_chunk (p);
new_size = chunksize (p) + (MINSIZE - 2 * SIZE_SZ) + misalign;
assert (new_size > 0 && new_size < (long) (2 * MINSIZE));
if (!prev_inuse (p))
- new_size += p->prev_size;
+ new_size += prev_size (p);
assert (new_size > 0 && new_size < HEAP_MAX_SIZE);
if (new_size + (HEAP_MAX_SIZE - prev_heap->size) < pad + MINSIZE + pagesz)
break;
@@ -682,9 +682,9 @@ _int_new_arena (size_t size)
LIBC_PROBE (memory_arena_new, 2, a, size);
mstate replaced_arena = thread_arena;
thread_arena = a;
- mutex_init (&a->mutex);
+ __libc_lock_init (a->mutex);
- (void) mutex_lock (&list_lock);
+ __libc_lock_lock (list_lock);
/* Add the new arena to the global list. */
a->next = main_arena.next;
@@ -694,11 +694,11 @@ _int_new_arena (size_t size)
atomic_write_barrier ();
main_arena.next = a;
- (void) mutex_unlock (&list_lock);
+ __libc_lock_unlock (list_lock);
- (void) mutex_lock (&free_list_lock);
+ __libc_lock_lock (free_list_lock);
detach_arena (replaced_arena);
- (void) mutex_unlock (&free_list_lock);
+ __libc_lock_unlock (free_list_lock);
/* Lock this arena. NB: Another thread may have been attached to
this arena because the arena is now accessible from the
@@ -710,7 +710,7 @@ _int_new_arena (size_t size)
but this could result in a deadlock with
__malloc_fork_lock_parent. */
- (void) mutex_lock (&a->mutex);
+ __libc_lock_lock (a->mutex);
return a;
}
@@ -724,7 +724,7 @@ get_free_list (void)
mstate result = free_list;
if (result != NULL)
{
- (void) mutex_lock (&free_list_lock);
+ __libc_lock_lock (free_list_lock);
result = free_list;
if (result != NULL)
{
@@ -736,12 +736,12 @@ get_free_list (void)
detach_arena (replaced_arena);
}
- (void) mutex_unlock (&free_list_lock);
+ __libc_lock_unlock (free_list_lock);
if (result != NULL)
{
LIBC_PROBE (memory_arena_reuse_free_list, 1, result);
- (void) mutex_lock (&result->mutex);
+ __libc_lock_lock (result->mutex);
thread_arena = result;
}
}
@@ -786,7 +786,7 @@ reused_arena (mstate avoid_arena)
result = next_to_use;
do
{
- if (!arena_is_corrupt (result) && !mutex_trylock (&result->mutex))
+ if (!arena_is_corrupt (result) && !__libc_lock_trylock (result->mutex))
goto out;
/* FIXME: This is a data race, see _int_new_arena. */
@@ -813,14 +813,14 @@ reused_arena (mstate avoid_arena)
/* No arena available without contention. Wait for the next in line. */
LIBC_PROBE (memory_arena_reuse_wait, 3, &result->mutex, result, avoid_arena);
- (void) mutex_lock (&result->mutex);
+ __libc_lock_lock (result->mutex);
out:
/* Attach the arena to the current thread. */
{
/* Update the arena thread attachment counters. */
mstate replaced_arena = thread_arena;
- (void) mutex_lock (&free_list_lock);
+ __libc_lock_lock (free_list_lock);
detach_arena (replaced_arena);
/* We may have picked up an arena on the free list. We need to
@@ -835,7 +835,7 @@ out:
++result->attached_threads;
- (void) mutex_unlock (&free_list_lock);
+ __libc_lock_unlock (free_list_lock);
}
LIBC_PROBE (memory_arena_reuse, 2, result, avoid_arena);
@@ -906,17 +906,17 @@ arena_get_retry (mstate ar_ptr, size_t bytes)
LIBC_PROBE (memory_arena_retry, 2, bytes, ar_ptr);
if (ar_ptr != &main_arena)
{
- (void) mutex_unlock (&ar_ptr->mutex);
+ __libc_lock_unlock (ar_ptr->mutex);
/* Don't touch the main arena if it is corrupt. */
if (arena_is_corrupt (&main_arena))
return NULL;
ar_ptr = &main_arena;
- (void) mutex_lock (&ar_ptr->mutex);
+ __libc_lock_lock (ar_ptr->mutex);
}
else
{
- (void) mutex_unlock (&ar_ptr->mutex);
+ __libc_lock_unlock (ar_ptr->mutex);
ar_ptr = arena_get2 (bytes, ar_ptr);
}
@@ -931,7 +931,7 @@ arena_thread_freeres (void)
if (a != NULL)
{
- (void) mutex_lock (&free_list_lock);
+ __libc_lock_lock (free_list_lock);
/* If this was the last attached thread for this arena, put the
arena on the free list. */
assert (a->attached_threads > 0);
@@ -940,7 +940,7 @@ arena_thread_freeres (void)
a->next_free = free_list;
free_list = a;
}
- (void) mutex_unlock (&free_list_lock);
+ __libc_lock_unlock (free_list_lock);
}
}
text_set_element (__libc_thread_subfreeres, arena_thread_freeres);
diff --git a/malloc/hooks.c b/malloc/hooks.c
index caa1e70d13..12995d3f0c 100644
--- a/malloc/hooks.c
+++ b/malloc/hooks.c
@@ -192,7 +192,7 @@ mem2chunk_check (void *mem, unsigned char **magic_p)
((char *) p < mp_.sbrk_base ||
((char *) p + sz) >= (mp_.sbrk_base + main_arena.system_mem))) ||
sz < MINSIZE || sz & MALLOC_ALIGN_MASK || !inuse (p) ||
- (!prev_inuse (p) && (p->prev_size & MALLOC_ALIGN_MASK ||
+ (!prev_inuse (p) && ((prev_size (p) & MALLOC_ALIGN_MASK) != 0 ||
(contig && (char *) prev_chunk (p) < mp_.sbrk_base) ||
next_chunk (prev_chunk (p)) != p)))
return NULL;
@@ -215,9 +215,9 @@ mem2chunk_check (void *mem, unsigned char **magic_p)
offset != 0x20 && offset != 0x40 && offset != 0x80 && offset != 0x100 &&
offset != 0x200 && offset != 0x400 && offset != 0x800 && offset != 0x1000 &&
offset < 0x2000) ||
- !chunk_is_mmapped (p) || (p->size & PREV_INUSE) ||
- ((((unsigned long) p - p->prev_size) & page_mask) != 0) ||
- ((p->prev_size + sz) & page_mask) != 0)
+ !chunk_is_mmapped (p) || prev_inuse (p) ||
+ ((((unsigned long) p - prev_size (p)) & page_mask) != 0) ||
+ ((prev_size (p) + sz) & page_mask) != 0)
return NULL;
for (sz -= 1; (c = ((unsigned char *) p)[sz]) != magic; sz -= c)
@@ -291,9 +291,9 @@ malloc_check (size_t sz, const void *caller)
return NULL;
}
- (void) mutex_lock (&main_arena.mutex);
+ __libc_lock_lock (main_arena.mutex);
victim = (top_check () >= 0) ? _int_malloc (&main_arena, sz + 1) : NULL;
- (void) mutex_unlock (&main_arena.mutex);
+ __libc_lock_unlock (main_arena.mutex);
return mem2mem_check (victim, sz);
}
@@ -305,11 +305,11 @@ free_check (void *mem, const void *caller)
if (!mem)
return;
- (void) mutex_lock (&main_arena.mutex);
+ __libc_lock_lock (main_arena.mutex);
p = mem2chunk_check (mem, NULL);
if (!p)
{
- (void) mutex_unlock (&main_arena.mutex);
+ __libc_lock_unlock (main_arena.mutex);
malloc_printerr (check_action, "free(): invalid pointer", mem,
&main_arena);
@@ -317,12 +317,12 @@ free_check (void *mem, const void *caller)
}
if (chunk_is_mmapped (p))
{
- (void) mutex_unlock (&main_arena.mutex);
+ __libc_lock_unlock (main_arena.mutex);
munmap_chunk (p);
return;
}
_int_free (&main_arena, p, 1);
- (void) mutex_unlock (&main_arena.mutex);
+ __libc_lock_unlock (main_arena.mutex);
}
static void *
@@ -345,9 +345,9 @@ realloc_check (void *oldmem, size_t bytes, const void *caller)
free_check (oldmem, NULL);
return NULL;
}
- (void) mutex_lock (&main_arena.mutex);
+ __libc_lock_lock (main_arena.mutex);
const mchunkptr oldp = mem2chunk_check (oldmem, &magic_p);
- (void) mutex_unlock (&main_arena.mutex);
+ __libc_lock_unlock (main_arena.mutex);
if (!oldp)
{
malloc_printerr (check_action, "realloc(): invalid pointer", oldmem,
@@ -357,7 +357,7 @@ realloc_check (void *oldmem, size_t bytes, const void *caller)
const INTERNAL_SIZE_T oldsize = chunksize (oldp);
checked_request2size (bytes + 1, nb);
- (void) mutex_lock (&main_arena.mutex);
+ __libc_lock_lock (main_arena.mutex);
if (chunk_is_mmapped (oldp))
{
@@ -400,7 +400,7 @@ realloc_check (void *oldmem, size_t bytes, const void *caller)
if (newmem == NULL)
*magic_p ^= 0xFF;
- (void) mutex_unlock (&main_arena.mutex);
+ __libc_lock_unlock (main_arena.mutex);
return mem2mem_check (newmem, bytes);
}
@@ -440,13 +440,14 @@ memalign_check (size_t alignment, size_t bytes, const void *caller)
alignment = a;
}
- (void) mutex_lock (&main_arena.mutex);
+ __libc_lock_lock (main_arena.mutex);
mem = (top_check () >= 0) ? _int_memalign (&main_arena, alignment, bytes + 1) :
NULL;
- (void) mutex_unlock (&main_arena.mutex);
+ __libc_lock_unlock (main_arena.mutex);
return mem2mem_check (mem, bytes);
}
+#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_25)
/* 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 1fa9487f18..bd8a1d4bb8 100644
--- a/malloc/malloc.c
+++ b/malloc/malloc.c
@@ -273,8 +273,9 @@
#define MALLOC_DEBUG 0
#endif
-#define USE_TCACHE 1
-
+#ifndef USE_TCACHE
+#define USE_TCACHE 0
+#endif
#if USE_TCACHE
/* we want 64 entries */
#define MAX_TCACHE_SIZE (MALLOC_ALIGNMENT * 63)
@@ -1067,7 +1068,7 @@ typedef struct __malloc_trace_map_entry_s {
#define TRACE_COUNT_TO_MAPPING_IDX(count) ((count) % TRACE_N_PER_MAPPING)
/* Global mutex for the trace buffer tree itself. */
-mutex_t __malloc_trace_mutex;
+libc_lock_define_initialized (static, __malloc_trace_mutex);
/* Global counter, "full" when equal to TRACE_MAX_COUNT. Points to
the next available slot, so POST-INCREMENT it. */
@@ -1174,7 +1175,7 @@ __mtb_trace_record (void)
/* START W: Switch window. */
/* W1. Acquire the global window lock. */
- (void) mutex_lock (&__malloc_trace_mutex);
+ __libc_lock_lock (__malloc_trace_mutex);
/* W2. If the thread-local window number is not -1, decrement the reference
counter for the current thread window. */
@@ -1216,7 +1217,7 @@ __mtb_trace_record (void)
/* FIXME: Better handling of errors? */
__libc_message (0, "Can't open trace buffer file %s\n", __malloc_trace_filename);
atomic_store_release (&__malloc_trace_enabled, 0);
- (void) mutex_unlock (&__malloc_trace_mutex);
+ __libc_lock_unlock (__malloc_trace_mutex);
return;
}
@@ -1234,7 +1235,7 @@ __mtb_trace_record (void)
/* FIXME: Better handling of errors? */
__libc_message (0, "Can't map trace_buffer file %s\n", __malloc_trace_filename);
atomic_store_release (&__malloc_trace_enabled, 0);
- (void) mutex_unlock (&__malloc_trace_mutex);
+ __libc_lock_unlock (__malloc_trace_mutex);
return;
}
@@ -1253,7 +1254,7 @@ __mtb_trace_record (void)
__malloc_trace_last_num = new_window;
/* W8. Release the global window lock. */
- (void) mutex_unlock (&__malloc_trace_mutex);
+ __libc_lock_unlock (__malloc_trace_mutex);
/* W9. Continue at T1. */
goto alg_t1;
@@ -1302,7 +1303,7 @@ __malloc_trace_init (char *filename)
__malloc_trace_buffer = (__malloc_trace_map_entry *) mapping;
- mutex_init (&__malloc_trace_mutex);
+ __libc_lock_init (__malloc_trace_mutex);
__malloc_trace_count = 0;
__mtb_trace_entry (__MTB_TYPE_MAGIC, sizeof(void *), (void *)0x1234);
@@ -1457,8 +1458,8 @@ size_t __malloc_trace_sync (void) { return 0; }
struct malloc_chunk {
- INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
- INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
+ INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */
+ INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
@@ -1487,18 +1488,19 @@ struct malloc_chunk {
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Size of previous chunk, if allocated | |
+ | Size of previous chunk, if unallocated (P clear) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Size of chunk, in bytes |M|P|
+