Why is memcpy listed in the symbols despite not being explicitly used in my function?

Why is memcpy listed in the symbols despite not being explicitly used in my function?
typescript
Ethan Jackson

I noticed that compiling a file with a single simple function on a type involving nested unions somehow causes nm to list memcpy among the symbols as undefined ("U") despite memcpy not being used.

Why? Is there some implicit use of it due to the way my types are designed?

MRE:

  • MRE.h:
#ifndef MRE_H # define MRE_H # include <stddef.h> # define HISTORY_SIZE 1024 typedef enum e_call_ent_type { E_MALLOC_ENT = 0, E_FREE_ENT = 1, E_REALLOC_ENT = 2 } t_call_ent_type; typedef struct s_call_ent { t_call_ent_type type; } t_call_ent; typedef enum e_lock_ent_type { E_LOCKBASE_ENT = 0, E_UNLOCKBASE_ENT = 1 } t_lock_ent_type; typedef struct s_lock_ent { t_lock_ent_type type; } t_lock_ent; typedef enum e_ent_type { E_VOID_ENT = 0, E_CALL_ENT = 1, E_LOCK_ENT = 2 } t_ent_type; typedef struct s_ent { t_ent_type type; union u_ent { t_call_ent call_ent; t_lock_ent lock_ent; } ent; int errno_val; } t_ent; typedef struct s_history { t_ent entries[HISTORY_SIZE]; } t_history; t_history init_history(void); #endif
  • init_history.c:
#include "MRE.h" t_history init_history(void) { t_history res; res.entries[0].type = E_VOID_ENT; return (res); }

Compiling and running nm:

> gcc -Wall -Werror -Wextra -c -o init_history.o init_history.c > nm init_history.o 0000000000000000 T init_history U memcpy U __stack_chk_fail

Update:

  • objdump -d init_history.o:
init_history.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <init_history>: 0: f3 0f 1e fa endbr64 4: 55 push %rbp 5: 48 89 e5 mov %rsp,%rbp 8: 48 81 ec 00 10 00 00 sub $0x1000,%rsp f: 48 83 0c 24 00 orq $0x0,(%rsp) 14: 48 81 ec 00 10 00 00 sub $0x1000,%rsp 1b: 48 83 0c 24 00 orq $0x0,(%rsp) 20: 48 81 ec 00 10 00 00 sub $0x1000,%rsp 27: 48 83 0c 24 00 orq $0x0,(%rsp) 2c: 48 83 ec 20 sub $0x20,%rsp 30: 48 89 bd e8 cf ff ff mov %rdi,-0x3018(%rbp) 37: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 3e: 00 00 40: 48 89 45 f8 mov %rax,-0x8(%rbp) 44: 31 c0 xor %eax,%eax 46: c7 85 f0 cf ff ff 00 movl $0x0,-0x3010(%rbp) 4d: 00 00 00 50: 48 8b 85 e8 cf ff ff mov -0x3018(%rbp),%rax 57: 48 89 c1 mov %rax,%rcx 5a: 48 8d 85 f0 cf ff ff lea -0x3010(%rbp),%rax 61: ba 00 30 00 00 mov $0x3000,%edx 66: 48 89 c6 mov %rax,%rsi 69: 48 89 cf mov %rcx,%rdi 6c: e8 00 00 00 00 call 71 <init_history+0x71> 71: 48 8b 45 f8 mov -0x8(%rbp),%rax 75: 64 48 2b 04 25 28 00 sub %fs:0x28,%rax 7c: 00 00 7e: 74 05 je 85 <init_history+0x85> 80: e8 00 00 00 00 call 85 <init_history+0x85> 85: 48 8b 85 e8 cf ff ff mov -0x3018(%rbp),%rax 8c: c9 leave 8d: c3 ret

Answer

If you look at the assembly instructions at offsets 6c and 80, you'll see a pair of CALL instructions with opcodes e8 00 00 00 00. Note that the destination offset i.e. the last 4 bytes are all 0. These are external functions calls that will get resolved at link time.

If you link the object file into an executable and run objdump on the executable, you'll probably see that these instructions now point to memcpy.

Related Articles