2

Why does this result in a segmentation fault?

#include <stddef.h>
       
void *malloc(size_t s) {           
};                                                                                                    
int main() {  
  printf("lol");           
}

This is how I compile:

gcc -o l lol.c

My guess is that printf() calls malloc().

Rachid K.
  • 4,490
  • 3
  • 11
  • 30
  • 3
    yes, printf calls malloc – DaBler Sep 13 '22 at 13:12
  • note that "lol" is your format string. printf still needs a buffer for the formatted output. – stark Sep 13 '22 at 13:13
  • 6
    Your malloc doesn't even return a `void*` so the program has undefined behavior. – Ted Lyngmo Sep 13 '22 at 13:16
  • 5
    The names of standard library functions are **reserved** for use as identifiers with external linkage. Your program has undefined behavior on account of itself defining one of them (`malloc`) as the name of a function with external linkage. – John Bollinger Sep 13 '22 at 13:17
  • Placing a `printf` call inside the custom malloc should make the compiler whine about infinite recursion, which is how you know that printf must be calling the custom malloc. – Lundin Sep 13 '22 at 13:29
  • 1
    @JohnBollinger All major implementations explicitly allow replacing `malloc` and friends. Those that do not are probably worthless anyway. – n. m. could be an AI Sep 13 '22 at 13:36
  • @Lundin it doesnt give any warnings for me? Even with -Wall -Wextra -Werror – runningupthatroad Sep 13 '22 at 13:37
  • 3
    @runningupthatroad You'd need whole-program _including the standard library_ analysis to detect the infinite recursion at compilet time in this case. I'm not aware of any C library implementation that can (as of this post) safely be subjected to whole-program analysis. I can, however, reproduce an infinite recursion at runtime with the suggested change. – zwol Sep 13 '22 at 13:48
  • 1
    Missing `#include ` confuses the issue. – chux - Reinstate Monica Sep 13 '22 at 14:05
  • @runningupthatroad Hmm I managed to get that fairly easy yesterday but I can't reproduce it today. Maybe I did something silly like calling malloc from inside malloc or similar, which would also yield those warnings. – Lundin Sep 14 '22 at 06:59

3 Answers3

7

Per the C language specification, providing your own definitions of standard library functions as functions with external linkage (which is the default for functions) produces undefined behavior. Those names are reserved for such use (C17 7.1.3). You have observed one of many possible manifestations of such behavior.

You have at least four alternatives:

  1. Just use the standard library's implementation.
  2. Define your function with a different name. For example, my_malloc(). You will then need to use that name to call it, though you could disguise that by use of a macro.
  3. Declare your function static (giving it internal linkage). Then it can have the same name as a standard library function, but only functions defined in the same translation unit (roughly: source file) will be able to call it via that name.
  4. Engage implementation-specific provisions of your particular C implementation (see next).

Some C implementations make implementation-specific provision for programs to provide their own versions of at least some library functions. Glibc is one of these. However, such provisions are subject to significant limits.

  • First and foremost, you can expect that the implementation will require your replacement functions to provide the same binary interface and to correctly implement the behavior specified by the language. (Your function does not do the latter.)

  • Second, where the function is part of a set of related ones, as malloc is, you may find that the implementation requires you to replace the whole set. Indeed, Glibc docs say that "replacing malloc" involves providing replacements for all these functions: malloc, free, calloc, realloc. Your program does not do this, either. The Glibc docs recommend providing replacements for several other functions as well, with the suggestion that failure to do so, while not in itself compromising any Glibc functions, is likely to break some programs: aligned_alloc, cfree,* malloc_usable_size, memalign, posix_memalign, pvalloc, valloc. These latter are not relevant to your particular example, however.


*Required only by very old programs.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • This is wrong. "Undefined behaviour" is not the same as "you are not allowed". Undefined behaviour simply means that the standard provides no guarantees. This is fine if something else (e.g. a specific implementation) provides needed guarantees. Which is incidentally the case here. – n. m. could be an AI Sep 13 '22 at 13:42
  • @n.1.8e9-where's-my-sharem., I have refined my wording to be more precise, and additionally addressed the question of implementation specific provisions for replacing library functions. – John Bollinger Sep 13 '22 at 13:57
  • Replacing malloc and friends is such a widespread existing practice, it should have been codified in the standard long ago. – n. m. could be an AI Sep 13 '22 at 16:46
  • 1
    You can also use a wrapper. Define the function as `void *__wrap_malloc(size_t size)` and use --wrap=malloc flag – EserRose Sep 13 '22 at 17:34
4

Something in the standard library is calling malloc(), expecting it to return a usable memory address, and writing something to that address.

On Unixy (or at least Linuxy) platforms, when you define a library function in the main program, it overrides the one in any other library, even when a library calls it, even when the same library that defines it calls it.

Rachid K.
  • 4,490
  • 3
  • 11
  • 30
user253751
  • 57,427
  • 7
  • 48
  • 90
  • Most linkers for most systems, not only for unixoid targets, show this behavior. I still have to find one that does not do it. – the busybee Sep 13 '22 at 13:40
  • @thebusybee Windows doesn't do this across DLLs – user253751 Sep 13 '22 at 13:40
  • 1
    Windows is not a linker, and DLLs are a different concept than libraries used by a linker. DLLs references are resolved at run time, but we are talking about link time here. – the busybee Sep 13 '22 at 13:53
  • The OP's program crashes on Win10, compiled with MinGW, too. ;-) – the busybee Sep 13 '22 at 14:00
  • @thebusybee The Microsoft standard library is in a DLL (msvcrXX). MinGW may behave differently. – user253751 Sep 13 '22 at 14:15
  • Apparently MinGW uses Microsoft's environment, the crash message is "Thread 1 received signal SIGSEGV, Segmentation fault. 0xXXXXXXXXXXXX in msvcrt!memmove () from C:\WINDOWS\System32\msvcrt.dll". -- Are you aware that the concept of shared objects exists in Unixoid systems, too? – the busybee Sep 13 '22 at 14:20
  • @thebusybee Unixoid shared objects allow their symbols to be overridden by the host application. Windowsoid DLLs do not. Probably whatever calls malloc is in the application and not in the DLL, but I don't know what that code would be, since printf is in the DLL. Startup code, perhaps. – user253751 Sep 13 '22 at 14:22
  • This is all fine and correct. However, we are still talking about a link time problem independent of the usage of shared libraries, not a run time issue. It does not matter what code calls the application's defect `malloc()`. – the busybee Sep 13 '22 at 14:32
  • @thebusybee code in msvcrt.dll will not call the application's malloc – user253751 Sep 13 '22 at 14:36
  • Sure, because when the DLL was linked, its calls of `malloc()` were not resolved to some future application's implementation. – the busybee Sep 13 '22 at 14:40
0

To verify that your malloc() function overrides the sibling one in the C library and is called by printf(), you can log a trace on the terminal (file descriptor number 1) by calling write() system call:

#include <stdio.h>
#include <unistd.h>  

void *malloc(size_t s) {
  write(1, "I am called\n", sizeof("I am called\n") - 1);           
};                                                                                                    
int main() {  
  printf("lol");           
}

After compilation, the programs displays:

$ gcc -g overm.c -o overm
$ ./overm
I am called
Segmentation fault (core dumped)

The analysis of the generated core dump file with a debugger like gdb shows the state of the call stack at the time of the crash:

$ gdb overm core
[...]
Reading symbols from overm...
[New LWP 8494]
Core was generated by `./overm'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x00007fd4bf7d6dc8 in _IO_new_file_overflow (f=0x7fd4bf9336a0 <_IO_2_1_stdout_>, ch=108) at fileops.c:781
781 fileops.c: No such file or directory.
(gdb) where
#0  0x00007fd4bf7d6dc8 in _IO_new_file_overflow (f=0x7fd4bf9336a0 <_IO_2_1_stdout_>, ch=108) at fileops.c:781
#1  0x00007fd4bf7d8024 in __GI__IO_default_xsputn (n=<optimized out>, data=<optimized out>, f=<optimized out>) at libioP.h:948
#2  __GI__IO_default_xsputn (f=f@entry=0x7fd4bf9336a0 <_IO_2_1_stdout_>, data=<optimized out>, n=n@entry=3) at genops.c:370
#3  0x00007fd4bf7d56fa in _IO_new_file_xsputn (n=3, data=<optimized out>, f=<optimized out>) at fileops.c:1265
#4  _IO_new_file_xsputn (f=0x7fd4bf9336a0 <_IO_2_1_stdout_>, data=<optimized out>, n=3) at fileops.c:1197
#5  0x00007fd4bf7bc972 in __vfprintf_internal (s=0x7fd4bf9336a0 <_IO_2_1_stdout_>, format=0x5556655db011 "lol", ap=ap@entry=0x7fff657394a0, 
    mode_flags=mode_flags@entry=0) at ../libio/libioP.h:948
#6  0x00007fd4bf7a7d3f in __printf (format=<optimized out>) at printf.c:33
#7  0x00005556655da1ab in main () at overm.c:8

The above display shows that the crash occured while calling printf() at line 33 of the source code. Let's set a breakpoint on the printf() function and rerun the program from the debugger:

(gdb) br printf
Breakpoint 3 at 0x7ffff7e1ac90: file printf.c, line 28.
(gdb) run
Breakpoint 3, __printf (format=0x555555556011 "lol") at printf.c:28
28  printf.c: No such file or directory.
(gdb) 

Once we are stopped at the printf() call, add another breakpoint onto malloc() function (we didn't do that before because malloc() is called several times during program startup by internal code) and continue the execution:

(gdb) br malloc
(gdb) continue
Continuing.

Breakpoint 4, malloc (s=140737354002065) at overm.c:4
4   void *malloc(size_t s) {
(gdb) where
#0  malloc (s=140737354002065) at overm.c:4
#1  0x00007ffff7e3ad04 in __GI__IO_file_doallocate (fp=0x7ffff7fa66a0 <_IO_2_1_stdout_>) at filedoalloc.c:101
#2  0x00007ffff7e4aed0 in __GI__IO_doallocbuf (fp=fp@entry=0x7ffff7fa66a0 <_IO_2_1_stdout_>) at libioP.h:948
#3  0x00007ffff7e49f30 in _IO_new_file_overflow (f=0x7ffff7fa66a0 <_IO_2_1_stdout_>, ch=-1) at fileops.c:745
#4  0x00007ffff7e486b5 in _IO_new_file_xsputn (n=3, data=<optimized out>, f=<optimized out>) at libioP.h:948
#5  _IO_new_file_xsputn (f=0x7ffff7fa66a0 <_IO_2_1_stdout_>, data=<optimized out>, n=3) at fileops.c:1197
#6  0x00007ffff7e2f972 in __vfprintf_internal (s=0x7ffff7fa66a0 <_IO_2_1_stdout_>, format=0x555555556011 "lol", ap=ap@entry=0x7fffffffdd90, 
    mode_flags=mode_flags@entry=0) at ../libio/libioP.h:948
#7  0x00007ffff7e1ad3f in __printf (format=<optimized out>) at printf.c:33
#8  0x00005555555551ab in main () at overm.c:8
(gdb) 

The displayed stack when we are stopped at the malloc() call, shows that it is the result of the printf() call from line 33 in the source code.

Rachid K.
  • 4,490
  • 3
  • 11
  • 30