Hello,
I cannot debug more, because I would need to compile a debuggable version of libmalloc. My findings so far:
I enter in https://opensource.apple.com/source/libmalloc/libmalloc-317.140.5/src/malloc.c.auto.html free
Then, it enters in find_registered_zone
When inside find_registered_zone, I lose myself. I know that tcmalloc library is called because I can see that the malloc library asks the size (which is translated in the call to tcmalloc::mz_size). Then, tcmalloc (here: https://github.com/gperftools/gperftools/blob/master/src/libc_override_osx.h#L108) correctly calculates the size and returns 128
When back to the malloc library, I see that more code on find_registered_zone is executed, but at some point, instead of the tcmalloc zone, the default zone is returned and then free_definite_size is called on that. But... default_zone is the tc_malloc one, which doesn't have free_definite_size, and crash!
...
(lldb) thread step-inst
Process 95351 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into
frame #0: 0x00007ff81bb921f1 libsystem_malloc.dylib`free + 337
libsystem_malloc.dylib`free:
-> 0x7ff81bb921f1 <+337>: popq %rbp
0x7ff81bb921f2 <+338>: jmpq *%rax
0x7ff81bb921f4 <+340>: xorl %edi, %edi
0x7ff81bb921f6 <+342>: testl $0x140, 0x419face0(%rip) ; malloc_num_zones_allocated, imm = 0x140
Target 0: (crash) stopped.
(lldb) thread step-inst
Process 95351 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into
frame #0: 0x00007ff81bb921f2 libsystem_malloc.dylib`free + 338
libsystem_malloc.dylib`free:
-> 0x7ff81bb921f2 <+338>: jmpq *%rax
0x7ff81bb921f4 <+340>: xorl %edi, %edi
0x7ff81bb921f6 <+342>: testl $0x140, 0x419face0(%rip) ; malloc_num_zones_allocated, imm = 0x140
0x7ff81bb92200 <+352>: sete %dil
Target 0: (crash) stopped.
(lldb) register read rax --format dec
rax = 140703593735262 libsystem_malloc.dylib`default_zone_free_definite_size
(lldb) thread step-inst
Process 95351 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into
frame #0: 0x00007ff81bb9245e libsystem_malloc.dylib`default_zone_free_definite_size
libsystem_malloc.dylib`default_zone_free_definite_size:
-> 0x7ff81bb9245e <+0>: movq 0x41a16d63(%rip), %rdi ; lite_zone
0x7ff81bb92465 <+7>: testq %rdi, %rdi
0x7ff81bb92468 <+10>: jne 0x7ff81bb92474 ; <+22>
0x7ff81bb9246a <+12>: movq 0x419fa8b7(%rip), %rax ; malloc_zones
Target 0: (crash) stopped.
...
From tcmalloc's side, everything is returned as it should (128 for the size), and it recognizes the pointer as owned. How can I go deeper? Thanks!
Post
Replies
Boosts
Views
Activity
Yep, here it is:
crash_report.txt
It would be good to analyze clang-apple internals for such free...
Thanks!
Nat
Is there something else that may interfere? Because I have the same disassembly result, however, I have the crash:
(lldb) r
Process 8502 launched: '/Users/aple/workspace/gperftools/a.out' (x86_64)
malloc(128) returned 0x100604000
Process 8502 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
frame #0: 0x0000000000000000
error: memory read failed for 0x0
Target 0: (a.out) stopped.
(lldb) disas -n main
a.out`main:
...
0x100003f26 <+22>: movl $0x80, %edi
0x100003f2b <+27>: callq 0x100003f5e ; symbol stub for: malloc
0x100003f30 <+32>: movq %rax, -0x18(%rbp)
...
0x100003f46 <+54>: movq -0x18(%rbp), %rdi
0x100003f4a <+58>: callq 0x100003f58 ; symbol stub for: free
0x100003f4f <+63>: xorl %eax, %eax
...
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
* frame #0: 0x0000000000000000
frame #1: 0x0000000100003f4f a.out`main + 63
frame #2: 0x00000001000154fe dyld`start + 462
(lldb)
I am linking with -ltcmalloc. If I patch tcmalloc by providing a custom free_definite_size (which simply calls tcmalloc's free), then compile and link again the simple PoC, the segfault goes away. Unfortunately, I cannot go deeper with the debugger (i.e., I cannot see what is happening inside the called free). Thanks!
Hello!
After many iterations, we think we arrived at the heart of the issue, but we need assistance to dig it further (@eskimo, thanks for your help btw!).
TL;DR: apple-clang replaces calls to free(x) (where x was allocated with a fixed-size at compile-time) with free_definite_size(x). The problem is that, by definition in malloc.h, the callback free_definite_size can be NULL (and it is correctly checked in malloc.c: https://opensource.apple.com/source/libmalloc/libmalloc-317.140.5/src/malloc.c.auto.html, function void free(void* ptr)).
tcmalloc doesn't implement free_definite_size: https://github.com/gperftools/gperftools/blob/master/src/libc_override_osx.h#L275 . Any program which does allocation/deallocation of fixed size, for example:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
void *ptr = malloc(128);
printf("malloc(128) returned %p\n",ptr);
free(ptr); /* replaced by free_definite_size(ptr, 128); */
return 0;
}
It will crash with tcmalloc (because of the lack of the callback for free_definite_size) but it runs fine without it. Solution: we can provide patches to tcmalloc (even if the maintainer doesn't answer), but I think either clang should be fixed, or the documentation updated with an error at compile time when registering the zone. Can someone help me to escalate this to Apple clang developers? Thanks!