__lll_mutex_lock_wait的错误原因

时间:2023-03-31 19:38:50

1. x86_64栈(glib 2.4):

free时:

(gdb) bt

#0  0x00002b9405ea1c38 in __lll_mutex_lock_wait () from /lib64/libc.so.6

#1  0x00002b9405e45e5f in _L_lock_4026 () from /lib64/libc.so.6

#2  0x00002b9405e42df1 in free () from /lib64/libc.so.6

#3  0x00002b9405e5b148 in tzset_internal () from /lib64/libc.so.6

#4  0x00002b9405e5b9d0 in tzset () from /lib64/libc.so.6

#5  0x00002b9405e5fe44 in strftime_l () from /lib64/libc.so.6

#6  0x00002b9405e93701 in __vsyslog_chk () from /lib64/libc.so.6

#7  0x00002b9405e3c6d0 in __libc_message () from /lib64/libc.so.6

#8  0x00002b9405e4177e in malloc_printerr () from /lib64/libc.so.6

#9  0x00002b9405e42dfc in free () from /lib64/libc.so.6

#10 0x00000000004007c9 in main (argc=1, argv=0x7fffa524f4d8) at x.cpp:17

malloc时:

#0  0x00002afca8597c38 in __lll_mutex_lock_wait () from /lib64/libc.so.6

#1  0x00002afca853be5f in _L_lock_4026 () from /lib64/libc.so.6

#2  0x00002afca8538df1 in free () from /lib64/libc.so.6

#3  0x00002afca8551148 in tzset_internal () from /lib64/libc.so.6

#4  0x00002afca85519d0 in tzset () from /lib64/libc.so.6

#5  0x00002afca8555e44 in strftime_l () from /lib64/libc.so.6

#6  0x00002afca8589701 in __vsyslog_chk () from /lib64/libc.so.6

#7  0x00002afca85326d0 in __libc_message () from /lib64/libc.so.6

#8  0x00002afca853777e in malloc_printerr () from /lib64/libc.so.6

#9  0x00002afca8539774 in _int_malloc () from /lib64/libc.so.6

#10 0x00002afca853b1b6 in malloc () from /lib64/libc.so.6

#11 0x00002afca8125fcd in operator new () from /usr/lib64/libstdc++.so.6

2. x86栈(glib 2.4):

(gdb) bt

#0  0xbfffe402 in __kernel_vsyscall ()

#1  0xb7e4a18e in __lll_mutex_lock_wait () from /lib/libc.so.6

#2  0xb7de8d81 in _L_mutex_lock_4119 () from /lib/libc.so.6

#3  0xb7e8d509 in __PRETTY_FUNCTION__.7757 () from /lib/libc.so.6

#4  0x00000000 in ?? ()

3. 测试代码1(两次free):

#include <stdio.h>

#include <stdlib.h>

#include <time.h>

#include <unistd.h>

int main()

{

setenv("LIBC_FATAL_STDERR_", "1", 1);

close(STDERR_FILENO);

time_t now = time(NULL);

struct tm* current = localtime(&now);

char* str = (char*)malloc(100);

free(str);

free(str);

return 0;

}

4. 测试代码2(越界写):

运行下面的代码,即可重现上面的__lll_mutex_lock_wait()问题:

1 // g++ -g -o x x.cpp

2 include <stdlib.h>

3 #include <unistd.h>

4 #include <time.h>

5

6 int main(int argc, char** argv)

7 {

8     setenv("LIBC_FATAL_STDERR_", "1", 1); // 让__libc_message()写stderr

9     close(STDERR_FILENO); // 让__libc_message()将出错写到系统日志

10

11     time_t now = time(NULL);

12     struct tm* t = localtime(&now);

13

14     char *p1 = new char[1024];

15     char *p2 = new char[4096];

16

17     p1[1024 + sizeof(size_t)] = 1; // 破坏p2的malloc_chunk的size成员

18     delete []p2;

19     delete []p1;

20     return 0;

21 }

当将上述代码中的“close(STDERR_FILENO)”注释掉后,亦即出错信息写标准输出:

// g++ -g -o b b.cpp

#include <stdlib.h>

#include <time.h>

#include <unistd.h>

int main(int argc, char** argv)

{

setenv("LIBC_FATAL_STDERR_", "1", 1);

//    close(STDERR_FILENO);

time_t now = time(NULL);

struct tm* current = localtime(&now);

char *p1 = new char[1024];

char *p2 = new char[4096];

p1[1024 + sizeof(size_t)] = 1;

delete []p2;

delete []p1;

return 0;

}

则看到的运行结果为:

*** glibc detected *** ./b: double free or corruption (!prev): 0x00000000005015a0 ***

======= Backtrace: =========

/lib64/libc.so.6[0x2acfbb87d77e]

/lib64/libc.so.6(__libc_free+0x6c)[0x2acfbb87edfc]

./b(__gxx_personality_v0+0x13f)[0x40076f]

/lib64/libc.so.6(__libc_start_main+0xf4)[0x2acfbb82f304]

./b(__gxx_personality_v0+0x39)[0x400669]

======= Memory map: ========

00400000-00401000 r-xp 00000000 08:01 1261572                            /tmp/X/b

00500000-00501000 rw-p 00000000 08:01 1261572                            /tmp/X/b

00501000-00522000 rw-p 00501000 00:00 0                                  [heap]

2acfbb295000-2acfbb2b0000 r-xp 00000000 08:01 229419                     /lib64/ld-2.4.so

2acfbb2b0000-2acfbb2b1000 rw-p 2acfbb2b0000 00:00 0

2acfbb2bf000-2acfbb2c0000 rw-p 2acfbb2bf000 00:00 0

2acfbb3af000-2acfbb3b1000 rw-p 0001a000 08:01 229419                     /lib64/ld-2.4.so

2acfbb3b1000-2acfbb494000 r-xp 00000000 08:01 529172                     /usr/lib64/libstdc++.so.6.0.8

2acfbb494000-2acfbb594000 ---p 000e3000 08:01 529172                     /usr/lib64/libstdc++.so.6.0.8

2acfbb594000-2acfbb59a000 r--p 000e3000 08:01 529172                     /usr/lib64/libstdc++.so.6.0.8

2acfbb59a000-2acfbb59d000 rw-p 000e9000 08:01 529172                     /usr/lib64/libstdc++.so.6.0.8

2acfbb59d000-2acfbb5af000 rw-p 2acfbb59d000 00:00 0

2acfbb5af000-2acfbb603000 r-xp 00000000 08:01 229463                     /lib64/libm-2.4.so

2acfbb603000-2acfbb702000 ---p 00054000 08:01 229463                     /lib64/libm-2.4.so

2acfbb702000-2acfbb704000 rw-p 00053000 08:01 229463                     /lib64/libm-2.4.so

2acfbb704000-2acfbb711000 r-xp 00000000 08:01 229456                     /lib64/libgcc_s.so.1

2acfbb711000-2acfbb810000 ---p 0000d000 08:01 229456                     /lib64/libgcc_s.so.1

2acfbb810000-2acfbb811000 rw-p 0000c000 08:01 229456                     /lib64/libgcc_s.so.1

2acfbb811000-2acfbb812000 rw-p 2acfbb811000 00:00 0

2acfbb812000-2acfbb948000 r-xp 00000000 08:01 229436                     /lib64/libc-2.4.so

2acfbb948000-2acfbba48000 ---p 00136000 08:01 229436                     /lib64/libc-2.4.so

2acfbba48000-2acfbba4b000 r--p 00136000 08:01 229436                     /lib64/libc-2.4.so

2acfbba4b000-2acfbba4d000 rw-p 00139000 08:01 229436                     /lib64/libc-2.4.so

2acfbba4d000-2acfbba53000 rw-p 2acfbba4d000 00:00 0

2acfbbb00000-2acfbbb21000 rw-p 2acfbbb00000 00:00 0

2acfbbb21000-2acfbbc00000 ---p 2acfbbb21000 00:00 0

7fffef800000-7fffef815000 rw-p 7fffef800000 00:00 0                      [stack]

ffffffffff600000-ffffffffffe00000 ---p 00000000 00:00 0                  [vdso]

Aborted (core dumped)

并会产生core文件:

(gdb) bt

#0  0x00002abaa26ddf45 in raise () from /lib64/libc.so.6

#1  0x00002abaa26df340 in abort () from /lib64/libc.so.6

#2  0x00002abaa271478b in __libc_message () from /lib64/libc.so.6

#3  0x00002abaa271977e in malloc_printerr () from /lib64/libc.so.6

#4  0x00002abaa271adfc in free () from /lib64/libc.so.6

#5  0x000000000040076f in main (argc=1, argv=0x7fff08975c28) at b.cpp:18

如果将上面代码中的“delete [] p1;”和“delete [] p2;”先后顺序对调一下:

// g++ -g -o b b.cpp

#include <stdlib.h>

#include <time.h>

#include <unistd.h>

int main(int argc, char** argv)

{

setenv("LIBC_FATAL_STDERR_", "1", 1);

//close(STDERR_FILENO);

time_t now = time(NULL);

struct tm* current = localtime(&now);

char *p1 = new char[1024];

char *p2 = new char[4096];

p1[1024 + sizeof(size_t)] = 1;

delete []p1; // core在这

delete []p2;

return 0;

}

则运行时core:

(gdb) bt

#0  0x00002b6e669df99e in _int_free () from /lib64/libc.so.6

#1  0x00002b6e669dfdfc in free () from /lib64/libc.so.6

#2  0x000000000040076f in main (argc=1, argv=0x7fff446b0938) at d.cpp:18

如果将代码改成如下(不调用time相关函数):

// g++ -g -o b b.cpp

#include <stdlib.h>

#include <time.h>

#include <unistd.h>

int main(int argc, char** argv)

{

setenv("LIBC_FATAL_STDERR_", "1", 1);

close(STDERR_FILENO);

//time_t now = time(NULL);

//struct tm* current = localtime(&now);

char *p1 = new char[1024];

char *p2 = new char[4096];

p1[1024 + sizeof(size_t)] = 1;

delete [] p2;

delete [] p1;

return 0;

}

运行时会core:

(gdb) bt

#0  0x00002ad55252af45 in raise () from /lib64/libc.so.6

#1  0x00002ad55252c340 in abort () from /lib64/libc.so.6

#2  0x00002ad55256178b in __libc_message () from /lib64/libc.so.6

#3  0x00002ad55256677e in malloc_printerr () from /lib64/libc.so.6

#4  0x00002ad552567dfc in free () from /lib64/libc.so.6

#5  0x000000000040070e in main (argc=1, argv=0x7fff58b28db8) at d.cpp:18

同时,系统日志文件/var/log/messages会包含(被认为已free过):

Oct  9 15:06:17 Tencent64 d: *** glibc detected *** /tmp/X/d: double free or corruption (!prev): 0x0000000000501670 ***

5. malloc_chunk结构(可以glibc的malloc.c中找到):

有两种结构:

malloc_chunk相关的源代码:

#ifndef Void_t

#if (__STD_C || defined(WIN32))

#define Void_t      void

#else

#define Void_t      char

#endif

#endif /*Void_t*/

#define INTERNAL_SIZE_T size_t

#define SIZE_SZ                (sizeof(INTERNAL_SIZE_T))

/* size field is or'ed with PREV_INUSE when previous adjacent chunk in use */

#define PREV_INUSE 0x1

/* size field is or'ed with IS_MMAPPED if the chunk was obtained with mmap() */

#define IS_MMAPPED 0x2

/* size field is or'ed with NON_MAIN_ARENA if the chunk was obtained

from a non-main arena.  This is only set immediately before handing

the chunk to the user, if necessary.  */

#define NON_MAIN_ARENA 0x4

static struct malloc_state main_arena;

/* pad request bytes into a usable size -- internal version */

#define request2size(req)                                         \

(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE)  ?             \

MINSIZE :                                                      \

((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

/*  Same, except also perform argument check */

#define checked_request2size(req, sz)                             \

if (REQUEST_OUT_OF_RANGE(req)) {                                \

MALLOC_FAILURE_ACTION;                                        \

return 0;                                                     \

}                                                               \

(sz) = request2size(req);

/*

Bits to mask off when extracting size

Note: IS_MMAPPED is intentionally not masked off from size field in

macros for which mmapped chunks should never be seen. This should

cause helpful core dumps to occur if it is tried by accident by

people extending or adapting this malloc.

*/

#define SIZE_BITS (PREV_INUSE|IS_MMAPPED|NON_MAIN_ARENA)

/* extract inuse bit of previous chunk */

#define prev_inuse(p)       ((p)->size & PREV_INUSE)

/* Get size, ignoring use bits */

#define chunksize(p)         ((p)->size & ~(SIZE_BITS))

typedef struct malloc_chunk* mfastbinptr;

#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[idx])

/*

This struct declaration is misleading (but accurate and necessary).

It declares a "view" into memory allowing access to necessary

fields at known offsets from a given base. See explanation below.

*/

struct malloc_chunk {

INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */

INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */

struct malloc_chunk* fd;  /* double links -- used only if free, Forward pointer to next chunk in list. */

struct malloc_chunk* bk;  /* Back pointer to previous chunk in list */

/* Only used for large blocks: pointer to next larger size.  */

struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */

struct malloc_chunk* bk_nextsize;

};

#define chunk2mem(p)   ((Void_t*)((char*)(p) + 2*SIZE_SZ))

#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - 2*SIZE_SZ))

#define chunk_is_mmapped(p) ((p)->size & IS_MMAPPED)

#define chunk_non_main_arena(p) ((p)->size & NON_MAIN_ARENA)

/* Ptr to next physical malloc_chunk. */

#define next_chunk(p) ((mchunkptr)( ((char*)(p)) + ((p)->size & ~SIZE_BITS) ))

/* Ptr to previous physical malloc_chunk */

#define prev_chunk(p) ((mchunkptr)( ((char*)(p)) - ((p)->prev_size) ))

/* extract p's inuse bit */

#define inuse(p)\

((((mchunkptr)(((char*)(p))+((p)->size & ~SIZE_BITS)))->size) & PREV_INUSE)

/* set/clear chunk as being inuse without otherwise disturbing */

#define set_inuse(p)\

((mchunkptr)(((char*)(p)) + ((p)->size & ~SIZE_BITS)))->size |= PREV_INUSE

#define clear_inuse(p)\

((mchunkptr)(((char*)(p)) + ((p)->size & ~SIZE_BITS)))->size &= ~(PREV_INUSE)

6. 链表结构:

对于char* str = (char*)malloc(1024);,它的前8字节(x86上为4字节)分别为prev_size和size:

chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|             Size of previous chunk                            |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

`head:' |             Size of chunk, in bytes                         |P|

mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|             Forward pointer to next chunk in list             |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|             Back pointer to previous chunk in list            |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|             Unused space (may be 0 bytes long)                .

.                                                               .

.                                                               |

nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

`foot:' |             Size of chunk, in bytes                           |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

7. 函数malloc_printerr()源码

static void malloc_printerr(int action, const char *str, void *ptr)

{

if ((action & 5) == 5)

__libc_message (action & 2, "%s\n", str);

else if (action & 1)

{

char buf[2 * sizeof (uintptr_t) + 1];

buf[sizeof (buf) - 1] = '\0';

char *cp = _itoa_word ((uintptr_t) ptr, &buf[sizeof (buf) - 1], 16, 0);

while (cp > buf)

*--cp = '0';

__libc_message (action & 2,

"*** glibc detected *** %s: %s: 0x%s ***\n",

__libc_argv[0] ?: "<unknown>", str, cp);

}

else if (action & 2)

abort ();

}

8. 函数__libc_message()源码

void __libc_message(int do_abort, const char*  fmt, ...)

{

va_list ap;

va_list ap_copy;

int fd = -1;

va_start (ap, fmt);

va_copy (ap_copy, ap);

#ifdef FATAL_PREPARE

FATAL_PREPARE;

#endif

// 从下面代码可以看出,如果没有指定环境变量LIBC_FATAL_STDERR_,则错误输出到终端tty

// 如果指定了,则输出到标准出错,环境变量LIBC_FATAL_STDERR_的值可以为任意值,

// 写标准出错或终端失败时,就写系统日志。

// 测试代码调用了close(STDERR_FILENO),会导致前面的writev失败,从而写系统日志

/* Open a descriptor for /dev/tty unless the user explicitly

requests errors on standard error.  */

const char *on_2 = __secure_getenv ("LIBC_FATAL_STDERR_");

if (on_2 == NULL || *on_2 == '\0')

fd = open_not_cancel_2 (_PATH_TTY, O_RDWR | O_NOCTTY | O_NDELAY);

if (fd == -1)

fd = STDERR_FILENO;

struct str_list *list = NULL;

int nlist = 0;

const char *cp = fmt;

while (*cp != '\0')

{

/* Find the next "%s" or the end of the string.  */

const char *next = cp;

while (next[0] != '%' || next[1] != 's')

{

next = __strchrnul (next + 1, '%');

if (next[0] == '\0')

break;

}

/* Determine what to print.  */

const char *str;

size_t len;

if (cp[0] == '%' && cp[1] == 's')

{

str = va_arg (ap, const char *);

len = strlen (str);

cp += 2;

}

else

{

str = cp;

len = next - cp;

cp = next;

}

struct str_list *newp = alloca (sizeof (struct str_list));

newp->str = str;

newp->len = len;

newp->next = list;

list = newp;

++nlist;

}

bool written = false;

if (nlist > 0)

{

struct iovec *iov = alloca (nlist * sizeof (struct iovec));

ssize_t total = 0;

for (int cnt = nlist - 1; cnt >= 0; --cnt)

{

iov[cnt].iov_base = (void *) list->str;

iov[cnt].iov_len = list->len;

total += list->len;

list = list->next;

}

INTERNAL_SYSCALL_DECL (err);

ssize_t cnt;

do

cnt = INTERNAL_SYSCALL (writev, err, 3, fd, iov, nlist);

while (INTERNAL_SYSCALL_ERROR_P (cnt, err)

&& INTERNAL_SYSCALL_ERRNO (cnt, err) == EINTR);

if (cnt == total)

written = true;

}

va_end (ap);

/* If we  had no success writing the message, use syslog.  */

if (! written)

vsyslog (LOG_ERR, fmt, ap_copy); // 写标准出错或终端失败时,就写系统日志

// 测试代码调用了close(STDERR_FILENO),会导致前面的writev失败,从而写系统日志

va_end (ap_copy);

if (do_abort)

{

if (do_abort > 1 && written)

{

void *addrs[64];

#define naddrs (sizeof (addrs) / sizeof (addrs[0]))

int n = __backtrace (addrs, naddrs);

if (n > 2)

{

#define strnsize(str) str, strlen (str)

#define writestr(str) write_not_cancel (fd, str)

writestr (strnsize ("======= Backtrace: =========\n"));

__backtrace_symbols_fd (addrs + 1, n - 1, fd);

writestr (strnsize ("======= Memory map: ========\n"));

int fd2 = open_not_cancel_2 ("/proc/self/maps", O_RDONLY);

char buf[1024];

ssize_t n2;

while ((n2 = read_not_cancel (fd2, buf, sizeof (buf))) > 0)

if (write_not_cancel (fd, buf, n2) != n2)

break;

close_not_cancel_no_status (fd2);

}

}

/* Terminate the process.  */

abort ();

}

}

9. 函数_int_free()源代码

static void

#ifdef ATOMIC_FASTBINS

_int_free(mstate av, mchunkptr p, int have_lock)

#else

_int_free(mstate av, mchunkptr p)

#endif

{

INTERNAL_SIZE_T size;        /* its size */

mfastbinptr*    fb;          /* associated fastbin */

mchunkptr       nextchunk;   /* next contiguous chunk */

INTERNAL_SIZE_T nextsize;    /* its size */

int             nextinuse;   /* true if nextchunk is used */

INTERNAL_SIZE_T prevsize;    /* size of previous contiguous chunk */

mchunkptr       bck;         /* misc temp for linking */

mchunkptr       fwd;         /* misc temp for linking */

const char *errstr = NULL;

#ifdef ATOMIC_FASTBINS

int locked = 0;

#endif

size = chunksize(p);

/* Little security check which won't hurt performance: the

allocator never wrapps around at the end of the address space.

Therefore we can exclude some size values which might appear

here by accident or by "design" from some intruder.  */

if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)

|| __builtin_expect (misaligned_chunk (p), 0))

{

errstr = "free(): invalid pointer";

errout:

#ifdef ATOMIC_FASTBINS

if (! have_lock && locked)

(void)mutex_unlock(&av->mutex);

#endif

malloc_printerr (check_action, errstr, chunk2mem(p));

return;

}

/* We know that each chunk is at least MINSIZE bytes in size.  */

if (__builtin_expect (size < MINSIZE, 0))

{

errstr = "free(): invalid size";

goto errout;

}

check_inuse_chunk(av, p);

/*

If eligible, place chunk on a fastbin so it can be found

and used quickly in malloc.

*/

if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())

#if TRIM_FASTBINS

/*

If TRIM_FASTBINS set, don't place chunks

bordering top into fastbins

*/

&& (chunk_at_offset(p, size) != av->top)

#endif

) {

if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0)

|| __builtin_expect (chunksize (chunk_at_offset (p, size))

>= av->system_mem, 0))

{

#ifdef ATOMIC_FASTBINS

/* We might not have a lock at this point and concurrent modifications

of system_mem might have let to a false positive.  Redo the test

after getting the lock.  */

if (have_lock

|| ({ assert (locked == 0);

mutex_lock(&av->mutex);

locked = 1;

chunk_at_offset (p, size)->size <= 2 * SIZE_SZ

|| chunksize (chunk_at_offset (p, size)) >= av->system_mem;

}))

#endif

{

errstr = "free(): invalid next size (fast)";

goto errout;

}

#ifdef ATOMIC_FASTBINS

if (! have_lock)

{

(void)mutex_unlock(&av->mutex);

locked = 0;

}

#endif

}

if (__builtin_expect (perturb_byte, 0))

free_perturb (chunk2mem(p), size - 2 * SIZE_SZ);

set_fastchunks(av);

unsigned int idx = fastbin_index(size);

fb = &fastbin (av, idx);

#ifdef ATOMIC_FASTBINS

mchunkptr fd;

mchunkptr old = *fb;

unsigned int old_idx = ~0u;

do

{

/* Another simple check: make sure the top of the bin is not the

record we are going to add (i.e., double free).  */

if (__builtin_expect (old == p, 0))

{

errstr = "double free or corruption (fasttop)";

goto errout;

}

if (old != NULL)

old_idx = fastbin_index(chunksize(old));

p->fd = fd = old;

}

while ((old = catomic_compare_and_exchange_val_rel (fb, p, fd)) != fd);

if (fd != NULL && __builtin_expect (old_idx != idx, 0))

{

errstr = "invalid fastbin entry (free)";

goto errout;

}

#else

/* Another simple check: make sure the top of the bin is not the

record we are going to add (i.e., double free).  */

if (__builtin_expect (*fb == p, 0))

{

errstr = "double free or corruption (fasttop)";

goto errout;

}

if (*fb != NULL

&& __builtin_expect (fastbin_index(chunksize(*fb)) != idx, 0))

{

errstr = "invalid fastbin entry (free)";

goto errout;

}

p->fd = *fb;

*fb = p;

#endif

}

/*

Consolidate other non-mmapped chunks as they arrive.

*/

else if (!chunk_is_mmapped(p)) {

#ifdef ATOMIC_FASTBINS

if (! have_lock) {

# if THREAD_STATS

if(!mutex_trylock(&av->mutex))

++(av->stat_lock_direct);

else {

(void)mutex_lock(&av->mutex);

++(av->stat_lock_wait);

}

# else

(void)mutex_lock(&av->mutex);

# endif

locked = 1;

}

#endif

nextchunk = chunk_at_offset(p, size);

/* Lightweight tests: check whether the block is already the

top block.  */

if (__builtin_expect (p == av->top, 0))

{

errstr = "double free or corruption (top)";

goto errout;

}

/* Or whether the next chunk is beyond the boundaries of the arena.  */

if (__builtin_expect (contiguous (av)

&& (char *) nextchunk

>= ((char *) av->top + chunksize(av->top)), 0))

{

errstr = "double free or corruption (out)";

goto errout;

}

/* Or whether the block is actually not marked used.  */

if (__builtin_expect (!prev_inuse(nextchunk), 0))

{

errstr = "double free or corruption (!prev)";

goto errout;

}

nextsize = chunksize(nextchunk);

if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0)

|| __builtin_expect (nextsize >= av->system_mem, 0))

{

errstr = "free(): invalid next size (normal)";

goto errout;

}

if (__builtin_expect (perturb_byte, 0))

free_perturb (chunk2mem(p), size - 2 * SIZE_SZ);

/* consolidate backward */

if (!prev_inuse(p)) {

prevsize = p->prev_size;

size += prevsize;

p = chunk_at_offset(p, -((long) prevsize));

unlink(p, bck, fwd);

}

if (nextchunk != av->top) {

/* get and clear inuse bit */

nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

/* consolidate forward */

if (!nextinuse) {

unlink(nextchunk, bck, fwd);

size += nextsize;

} else

clear_inuse_bit_at_offset(nextchunk, 0);

/*

Place the chunk in unsorted chunk list. Chunks are

not placed into regular bins until after they have

been given one chance to be used in malloc.

*/

bck = unsorted_chunks(av);

fwd = bck->fd;

if (__builtin_expect (fwd->bk != bck, 0))

{

errstr = "free(): corrupted unsorted chunks";

goto errout;

}

p->fd = fwd;

p->bk = bck;

if (!in_smallbin_range(size))

{

p->fd_nextsize = NULL;

p->bk_nextsize = NULL;

}

bck->fd = p;

fwd->bk = p;

set_head(p, size | PREV_INUSE);

set_foot(p, size);

check_free_chunk(av, p);

}

/*

If the chunk borders the current high end of memory,

consolidate into top

*/

else {

size += nextsize;

set_head(p, size | PREV_INUSE);

av->top = p;

check_chunk(av, p);

}

/*

If freeing a large space, consolidate possibly-surrounding

chunks. Then, if the total unused topmost memory exceeds trim

threshold, ask malloc_trim to reduce top.

Unless max_fast is 0, we don't know if there are fastbins

bordering top, so we cannot tell for sure whether threshold

has been reached unless fastbins are consolidated.  But we

don't want to consolidate on each free.  As a compromise,

consolidation is performed if FASTBIN_CONSOLIDATION_THRESHOLD

is reached.

*/

if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD) {

if (have_fastchunks(av))

malloc_consolidate(av);

if (av == &main_arena) {

#ifndef MORECORE_CANNOT_TRIM

if ((unsigned long)(chunksize(av->top)) >=

(unsigned long)(mp_.trim_threshold))

sYSTRIm(mp_.top_pad, av);

#endif

} else {

/* Always try heap_trim(), even if the top chunk is not

large, because the corresponding heap might go away.  */

heap_info *heap = heap_for_ptr(top(av));

assert(heap->ar_ptr == av);

heap_trim(heap, mp_.top_pad);

}

}

#ifdef ATOMIC_FASTBINS

if (! have_lock) {

assert (locked);

(void)mutex_unlock(&av->mutex);

}

#endif

}

/*

If the chunk was allocated via mmap, release via munmap(). Note

that if HAVE_MMAP is false but chunk_is_mmapped is true, then

user must have overwritten memory. There's nothing we can do to

catch this error unless MALLOC_DEBUG is set, in which case

check_inuse_chunk (above) will have triggered error.

*/

else {

#if HAVE_MMAP

munmap_chunk (p);

#endif

}

}

10. 测试malloc_chunk代码

// chunk2mem()等相关实现,请从malloc_chunk结构一节摘取,

// 或直接从malloc.c文件取:

// http://code.woboq.org/userspace/glibc/malloc/malloc.c.html

void print_malloc_chunk(const char* tag, mchunkptr chunk)

{

printf("\n");

printf("[%s]p: %p\n", tag, chunk2mem(chunk));

printf("[%s]prev_size: %u, used(%d)\n", tag, chunk->prev_size, prev_inuse(chunk));

printf("[%s]size: %u/%u, used(%d)\n", tag, chunk->size, chunksize(chunk), inuse(chunk));

printf("[%s]Forward pointer: %p\n", tag, chunk->fd);

printf("[%s]Back pointer: %p\n", tag, chunk->bk);

if (chunk->fd != NULL)

{

printf("[%s]Forward prev_size: %u, used(%d)\n", tag, chunk->fd->prev_size, inuse(chunk->fd));

printf("[%s]Forward size: %u, used(%d)\n", tag,  chunksize(chunk->fd), inuse(chunk->fd));

}

}

int main(int argc, char** argv)

{

char *p1 = new char[1024];

char *p2 = new char[4096];

char *p3 = new char[2048];

mchunkptr c1 = mem2chunk(p1);

mchunkptr c2 = mem2chunk(p2);

mchunkptr c3 = mem2chunk(p3);

print_malloc_chunk("c11", c1);

print_malloc_chunk("c21", c2);

print_malloc_chunk("c31", c3);

printf("----------------------------------\n");

delete []p2;

print_malloc_chunk("c12", c1);

print_malloc_chunk("c22", c2);

print_malloc_chunk("c32", c3);

delete []p3;

delete []p1;

return 0;

}

运行输出:

[c11]p: 0x501010

[c11]prev_size: 0, used(1)

[c11]size: 1041/1040, used(1)

[c11]Forward pointer: (nil)

[c11]Back pointer: (nil)

[c21]p: 0x501420

[c21]prev_size: 0, used(1)

[c21]size: 4113/4112, used()

[c21]Forward pointer: (nil)

[c21]Back pointer: (nil)

[c31]p: 0x502430

[c31]prev_size: 0, used(1)

[c31]size: 2065/2064, used(1)

[c31]Forward pointer: (nil)

[c31]Back pointer: (nil)

----------------------------------

[c12]p: 0x501010

[c12]prev_size: 0, used(1)

[c12]size: 1041/1040, used(1)

[c12]Forward pointer: (nil)

[c12]Back pointer: (nil)

[c22]p: 0x501420

[c22]prev_size: 0, used(1)

[c22]size: 4113/4112, used() // 这里发生了变化,因为“delete []p2”将p2释放了,所以为free

[c22]Forward pointer: 0x2ada9e7539f0

[c22]Back pointer: 0x2ada9e7539f0

[c22]Forward prev_size: 0, used(0)

[c22]Forward size: 0, used(0)

[c32]p: 0x502430

[c32]prev_size: 4112, used(0) // 这里也发生了变化,为p2的chunk大小和状态

[c32]size: 2064/2064, used(1)

[c32]Forward pointer: (nil)

[c32]Back pointer: (nil)

11. 函数_int_malloc()源代码

static Void_t*

_int_malloc(mstate av, size_t bytes)

{

INTERNAL_SIZE_T nb;               /* normalized request size */

unsigned int    idx;              /* associated bin index */

mbinptr         bin;              /* associated bin */

mchunkptr       victim;           /* inspected/selected chunk */

INTERNAL_SIZE_T size;             /* its size */

int             victim_index;     /* its bin index */

mchunkptr       remainder;        /* remainder from a split */

unsigned long   remainder_size;   /* its size */

unsigned int    block;            /* bit map traverser */

unsigned int    bit;              /* bit map traverser */

unsigned int    map;              /* current word of binmap */

mchunkptr       fwd;              /* misc temp for linking */

mchunkptr       bck;              /* misc temp for linking */

const char *errstr = NULL;

/*

Convert request size to internal form by adding SIZE_SZ bytes

overhead plus possibly more to obtain necessary alignment and/or

to obtain a size of at least MINSIZE, the smallest allocatable

size. Also, checked_request2size traps (returning 0) request sizes

that are so large that they wrap around zero when padded and

aligned.

*/

checked_request2size(bytes, nb);

/*

If the size qualifies as a fastbin, first check corresponding bin.

This code is safe to execute even if av is not yet initialized, so we

can try it without checking, which saves some time on this fast path.

*/

if ((unsigned long)(nb) <= (unsigned long)(get_max_fast ())) {

idx = fastbin_index(nb);

mfastbinptr* fb = &fastbin (av, idx);

#ifdef ATOMIC_FASTBINS

mchunkptr pp = *fb;

do

{

victim = pp;

if (victim == NULL)

break;

}

while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))

!= victim);

#else

victim = *fb;

#endif

if (victim != 0) {

if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))

{

errstr = "malloc(): memory corruption (fast)";

errout:

malloc_printerr (check_action, errstr, chunk2mem (victim));

return NULL;

}

#ifndef ATOMIC_FASTBINS

*fb = victim->fd;

#endif

check_remalloced_chunk(av, victim, nb);

void *p = chunk2mem(victim);

if (__builtin_expect (perturb_byte, 0))

alloc_perturb (p, bytes);

return p;

}

}

/*

If a small request, check regular bin.  Since these "smallbins"

hold one size each, no searching within bins is necessary.

(For a large request, we need to wait until unsorted chunks are

processed to find best fit. But for small ones, fits are exact

anyway, so we can check now, which is faster.)

*/

if (in_smallbin_range(nb)) {

idx = smallbin_index(nb);

bin = bin_at(av,idx);

if ( (victim = last(bin)) != bin) {

if (victim == 0) /* initialization check */

malloc_consolidate(av);

else {

bck = victim->bk;

if (__builtin_expect (bck->fd != victim, 0))

{

errstr = "malloc(): smallbin double linked list corrupted";

goto errout;

}

set_inuse_bit_at_offset(victim, nb);

bin->bk = bck;

bck->fd = bin;

if (av != &main_arena)

victim->size |= NON_MAIN_ARENA;

check_malloced_chunk(av, victim, nb);

void *p = chunk2mem(victim);

if (__builtin_expect (perturb_byte, 0))

alloc_perturb (p, bytes);

return p;

}

}

}

/*

If this is a large request, consolidate fastbins before continuing.

While it might look excessive to kill all fastbins before

even seeing if there is space available, this avoids

fragmentation problems normally associated with fastbins.

Also, in practice, programs tend to have runs of either small or

large requests, but less often mixtures, so consolidation is not

invoked all that often in most programs. And the programs that

it is called frequently in otherwise tend to fragment.

*/

else {

idx = largebin_index(nb);

if (have_fastchunks(av))

malloc_consolidate(av);

}

/*

Process recently freed or remaindered chunks, taking one only if

it is exact fit, or, if this a small request, the chunk is remainder from

the most recent non-exact fit.  Place other traversed chunks in

bins.  Note that this step is the only place in any routine where

chunks are placed in bins.

The outer loop here is needed because we might not realize until

near the end of malloc that we should have consolidated, so must

do so and retry. This happens at most once, and only when we would

otherwise need to expand memory to service a "small" request.

*/

for(;;) {

int iters = 0;

while ( (victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) {

bck = victim->bk;

if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)

|| __builtin_expect (victim->size > av->system_mem, 0))

malloc_printerr (check_action, "malloc(): memory corruption",

chunk2mem (victim));

size = chunksize(victim);

/*

If a small request, try to use last remainder if it is the

only chunk in unsorted bin.  This helps promote locality for

runs of consecutive small requests. This is the only

exception to best-fit, and applies only when there is

no exact fit for a small chunk.

*/

if (in_smallbin_range(nb) &&

bck == unsorted_chunks(av) &&

victim == av->last_remainder &&

(unsigned long)(size) > (unsigned long)(nb + MINSIZE)) {

/* split and reattach remainder */

remainder_size = size - nb;

remainder = chunk_at_offset(victim, nb);

unsorted_chunks(av)->bk = unsorted_chunks(av)->fd = remainder;

av->last_remainder = remainder;

remainder->bk = remainder->fd = unsorted_chunks(av);

if (!in_smallbin_range(remainder_size))

{

remainder->fd_nextsize = NULL;

remainder->bk_nextsize = NULL;

}

set_head(victim, nb | PREV_INUSE |

(av != &main_arena ? NON_MAIN_ARENA : 0));

set_head(remainder, remainder_size | PREV_INUSE);

set_foot(remainder, remainder_size);

check_malloced_chunk(av, victim, nb);

void *p = chunk2mem(victim);

if (__builtin_expect (perturb_byte, 0))

alloc_perturb (p, bytes);

return p;

}

/* remove from unsorted list */

unsorted_chunks(av)->bk = bck;

bck->fd = unsorted_chunks(av);

/* Take now instead of binning if exact fit */

if (size == nb) {

set_inuse_bit_at_offset(victim, size);

if (av != &main_arena)

victim->size |= NON_MAIN_ARENA;

check_malloced_chunk(av, victim, nb);

void *p = chunk2mem(victim);

if (__builtin_expect (perturb_byte, 0))

alloc_perturb (p, bytes);

return p;

}

/* place chunk in bin */

if (in_smallbin_range(size)) {

victim_index = smallbin_index(size);

bck = bin_at(av, victim_index);

fwd = bck->fd;

}

else {

victim_index = largebin_index(size);

bck = bin_at(av, victim_index);

fwd = bck->fd;

/* maintain large bins in sorted order */

if (fwd != bck) {

/* Or with inuse bit to speed comparisons */

size |= PREV_INUSE;

/* if smaller than smallest, bypass loop below */

assert((bck->bk->size & NON_MAIN_ARENA) == 0);

if ((unsigned long)(size) < (unsigned long)(bck->bk->size)) {

fwd = bck;

bck = bck->bk;

victim->fd_nextsize = fwd->fd;

victim->bk_nextsize = fwd->fd->bk_nextsize;

fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;

}

else {

assert((fwd->size & NON_MAIN_ARENA) == 0);

while ((unsigned long) size < fwd->size)

{

fwd = fwd->fd_nextsize;

assert((fwd->size & NON_MAIN_ARENA) == 0);

}

if ((unsigned long) size == (unsigned long) fwd->size)

/* Always insert in the second position.  */

fwd = fwd->fd;

else

{

victim->fd_nextsize = fwd;

victim->bk_nextsize = fwd->bk_nextsize;

fwd->bk_nextsize = victim;

victim->bk_nextsize->fd_nextsize = victim;

}

bck = fwd->bk;

}

} else

victim->fd_nextsize = victim->bk_nextsize = victim;

}

mark_bin(av, victim_index);

victim->bk = bck;

victim->fd = fwd;

fwd->bk = victim;

bck->fd = victim;

#define MAX_ITERS 10000

if (++iters >= MAX_ITERS)

break;

}

/*

If a large request, scan through the chunks of current bin in

sorted order to find smallest that fits.  Use the skip list for this.

*/

if (!in_smallbin_range(nb)) {

bin = bin_at(av, idx);

/* skip scan if empty or largest chunk is too small */

if ((victim = first(bin)) != bin &&

(unsigned long)(victim->size) >= (unsigned long)(nb)) {

victim = victim->bk_nextsize;

while (((unsigned long)(size = chunksize(victim)) <

(unsigned long)(nb)))

victim = victim->bk_nextsize;

/* Avoid removing the first entry for a size so that the skip

list does not have to be rerouted.  */

if (victim != last(bin) && victim->size == victim->fd->size)

victim = victim->fd;

remainder_size = size - nb;

unlink(victim, bck, fwd);

/* Exhaust */

if (remainder_size < MINSIZE)  {

set_inuse_bit_at_offset(victim, size);

if (av != &main_arena)

victim->size |= NON_MAIN_ARENA;

}

/* Split */

else {

remainder = chunk_at_offset(victim, nb);

/* We cannot assume the unsorted list is empty and therefore

have to perform a complete insert here.  */

bck = unsorted_chunks(av);

fwd = bck->fd;

if (__builtin_expect (fwd->bk != bck, 0))

{

errstr = "malloc(): corrupted unsorted chunks";

goto errout;

}

remainder->bk = bck;

remainder->fd = fwd;

bck->fd = remainder;

fwd->bk = remainder;

if (!in_smallbin_range(remainder_size))

{

remainder->fd_nextsize = NULL;

remainder->bk_nextsize = NULL;

}

set_head(victim, nb | PREV_INUSE |

(av != &main_arena ? NON_MAIN_ARENA : 0));

set_head(remainder, remainder_size | PREV_INUSE);

set_foot(remainder, remainder_size);

}

check_malloced_chunk(av, victim, nb);

void *p = chunk2mem(victim);

if (__builtin_expect (perturb_byte, 0))

alloc_perturb (p, bytes);

return p;

}

}

/*

Search for a chunk by scanning bins, starting with next largest

bin. This search is strictly by best-fit; i.e., the smallest

(with ties going to approximately the least recently used) chunk

that fits is selected.

The bitmap avoids needing to check that most blocks are nonempty.

The particular case of skipping all bins during warm-up phases

when no chunks have been returned yet is faster than it might look.

*/

++idx;

bin = bin_at(av,idx);

block = idx2block(idx);

map = av->binmap[block];

bit = idx2bit(idx);

for (;;) {

/* Skip rest of block if there are no more set bits in this block.  */

if (bit > map || bit == 0) {

do {

if (++block >= BINMAPSIZE)  /* out of bins */

goto use_top;

} while ( (map = av->binmap[block]) == 0);

bin = bin_at(av, (block << BINMAPSHIFT));

bit = 1;

}

/* Advance to bin with set bit. There must be one. */

while ((bit & map) == 0) {

bin = next_bin(bin);

bit <<= 1;

assert(bit != 0);

}

/* Inspect the bin. It is likely to be non-empty */

victim = last(bin);

/*  If a false alarm (empty bin), clear the bit. */

if (victim == bin) {

av->binmap[block] = map &= ~bit; /* Write through */

bin = next_bin(bin);

bit <<= 1;

}

else {

size = chunksize(victim);

/*  We know the first chunk in this bin is big enough to use. */

assert((unsigned long)(size) >= (unsigned long)(nb));

remainder_size = size - nb;

/* unlink */

unlink(victim, bck, fwd);

/* Exhaust */

if (remainder_size < MINSIZE) {

set_inuse_bit_at_offset(victim, size);

if (av != &main_arena)

victim->size |= NON_MAIN_ARENA;

}

/* Split */

else {

remainder = chunk_at_offset(victim, nb);

/* We cannot assume the unsorted list is empty and therefore

have to perform a complete insert here.  */

bck = unsorted_chunks(av);

fwd = bck->fd;

if (__builtin_expect (fwd->bk != bck, 0))

{

errstr = "malloc(): corrupted unsorted chunks 2";

goto errout;

}

remainder->bk = bck;

remainder->fd = fwd;

bck->fd = remainder;

fwd->bk = remainder;

/* advertise as last remainder */

if (in_smallbin_range(nb))

av->last_remainder = remainder;

if (!in_smallbin_range(remainder_size))

{

remainder->fd_nextsize = NULL;

remainder->bk_nextsize = NULL;

}

set_head(victim, nb | PREV_INUSE |

(av != &main_arena ? NON_MAIN_ARENA : 0));

set_head(remainder, remainder_size | PREV_INUSE);

set_foot(remainder, remainder_size);

}

check_malloced_chunk(av, victim, nb);

void *p = chunk2mem(victim);

if (__builtin_expect (perturb_byte, 0))

alloc_perturb (p, bytes);

return p;

}

}

use_top:

/*

If large enough, split off the chunk bordering the end of memory

(held in av->top). Note that this is in accord with the best-fit

search rule.  In effect, av->top is treated as larger (and thus

less well fitting) than any other available chunk since it can

be extended to be as large as necessary (up to system

limitations).

We require that av->top always exists (i.e., has size >=

MINSIZE) after initialization, so if it would otherwise be

exhausted by current request, it is replenished. (The main

reason for ensuring it exists is that we may need MINSIZE space

to put in fenceposts in sysmalloc.)

*/

victim = av->top;

size = chunksize(victim);

if ((unsigned long)(size) >= (unsigned long)(nb + MINSIZE)) {

remainder_size = size - nb;

remainder = chunk_at_offset(victim, nb);

av->top = remainder;

set_head(victim, nb | PREV_INUSE |

(av != &main_arena ? NON_MAIN_ARENA : 0));

set_head(remainder, remainder_size | PREV_INUSE);

check_malloced_chunk(av, victim, nb);

void *p = chunk2mem(victim);

if (__builtin_expect (perturb_byte, 0))

alloc_perturb (p, bytes);

return p;

}

#ifdef ATOMIC_FASTBINS

/* When we are using atomic ops to free fast chunks we can get

here for all block sizes.  */

else if (have_fastchunks(av)) {

malloc_consolidate(av);

/* restore original bin index */

if (in_smallbin_range(nb))

idx = smallbin_index(nb);

else

idx = largebin_index(nb);

}

#else

/*

If there is space available in fastbins, consolidate and retry,

to possibly avoid expanding memory. This can occur only if nb is

in smallbin range so we didn't consolidate upon entry.

*/

else if (have_fastchunks(av)) {

assert(in_smallbin_range(nb));

malloc_consolidate(av);

idx = smallbin_index(nb); /* restore original bin index */

}

#endif

/*

Otherwise, relay to handle system-dependent cases

*/

else {

void *p = sYSMALLOc(nb, av);

if (p != NULL && __builtin_expect (perturb_byte, 0))

alloc_perturb (p, bytes);

return p;

}

}

}