Hi, the DWARF5 standard adds the `DW_TAG_call_site` into the debug information. It is used by the debugger to obtain more information about when a function called The problem that I'm having is that GCC (version 10.1.0) doesn't seem to generate a `DW_TAG_call_site` on indirect function calls (function pointers) that are tail optimized. Here I have the following piece of code: ``` #include <stdio.h> void __attribute__((noinline)) print_hello(void) { printf("hello\n"); } void __attribute__((noinline)) print_world(void) { printf("world\n"); } /* the important function, both call get tail optmized */ void __attribute__((noinline)) call(int argc, void (*f)(void)) { if (argc % 2 == 0 || argc % 3 == 0 || argc % 5 == 0) { f(); } else { print_hello(); } } int main(int argc, char** argv) { if (argc % 2 == 0) { call(argc, print_hello); } else { call(argc, print_world); } return 0; } ``` Using the following command to compile. $ gcc -v -save-temps -Wall -O2 -gdwarf-5 main.c ``` Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/opt/gcc/libexec/gcc/x86_64-pc-linux-gnu/11.0.0/lto-wrapper Target: x86_64-pc-linux-gnu Configured with: ../gcc/configure --prefix=/opt/gcc/ --enable-languages=c,c++ --with-isl --with-linker-hash-style=gnu --with-system-zlib --enable-__cxa_atexit --enable-cet=auto --enable-checking=release --enable-clocale=gnu --enable-default-pie --enable-default-ssp --enable-gnu-indirect-function --enable-gnu-unique-object --enable-install-libiberty --enable-linker-build-id --enable-lto --enable-multilib --enable-plugin --enable-shared --enable-threads=posix --disable-libssp --disable-libstdcxx-pch --disable-libunwind-exceptions --disable-werror Thread model: posix Supported LTO compression algorithms: zlib zstd gcc version 11.0.0 20200706 (experimental) (GCC) COLLECT_GCC_OPTIONS='-v' '-save-temps' '-Wall' '-O2' '-gdwarf-5' '-mtune=generic' '-march=x86-64' '-dumpdir' 'a-' /opt/gcc/libexec/gcc/x86_64-pc-linux-gnu/11.0.0/cc1 -E -quiet -v main.c -mtune=generic -march=x86-64 -Wall -gdwarf-5 -fworking-directory -O2 -fpch-preprocess -o a-main.i ignoring nonexistent directory "/opt/gcc/lib/gcc/x86_64-pc-linux-gnu/11.0.0/../../../../x86_64-pc-linux-gnu/include" #include "..." search starts here: #include <...> search starts here: /opt/gcc/lib/gcc/x86_64-pc-linux-gnu/11.0.0/include /usr/local/include /opt/gcc/include /opt/gcc/lib/gcc/x86_64-pc-linux-gnu/11.0.0/include-fixed /usr/include End of search list. COLLECT_GCC_OPTIONS='-v' '-save-temps' '-Wall' '-O2' '-gdwarf-5' '-mtune=generic' '-march=x86-64' '-dumpdir' 'a-' /opt/gcc/libexec/gcc/x86_64-pc-linux-gnu/11.0.0/cc1 -fpreprocessed a-main.i -quiet -dumpdir a- -dumpbase main.c -dumpbase-ext .c -mtune=generic -march=x86-64 -gdwarf-5 -O2 -Wall -version -o a-main.s GNU C17 (GCC) version 11.0.0 20200706 (experimental) (x86_64-pc-linux-gnu) compiled by GNU C version 11.0.0 20200706 (experimental), GMP version 6.2.0, MPFR version 4.0.2, MPC version 1.1.0, isl version isl-0.22.1-GMP GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 GNU C17 (GCC) version 11.0.0 20200706 (experimental) (x86_64-pc-linux-gnu) compiled by GNU C version 11.0.0 20200706 (experimental), GMP version 6.2.0, MPFR version 4.0.2, MPC version 1.1.0, isl version isl-0.22.1-GMP GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 Compiler executable checksum: ab66556b79ab5f25cdbd6eb42043f35c COLLECT_GCC_OPTIONS='-v' '-save-temps' '-Wall' '-O2' '-gdwarf-5' '-mtune=generic' '-march=x86-64' '-dumpdir' 'a-' as -v --64 -o a-main.o a-main.s GNU assembler version 2.34.0 (x86_64-pc-linux-gnu) using BFD version (GNU Binutils) 2.34.0 COMPILER_PATH=/opt/gcc/libexec/gcc/x86_64-pc-linux-gnu/11.0.0/:/opt/gcc/libexec/gcc/x86_64-pc-linux-gnu/11.0.0/:/opt/gcc/libexec/gcc/x86_64-pc-linux-gnu/:/opt/gcc/lib/gcc/x86_64-pc-linux-gnu/11.0.0/:/opt/gcc/lib/gcc/x86_64-pc-linux-gnu/ LIBRARY_PATH=/opt/gcc/lib/gcc/x86_64-pc-linux-gnu/11.0.0/:/opt/gcc/lib/gcc/x86_64-pc-linux-gnu/11.0.0/../../../../lib64/:/lib/../lib64/:/usr/lib/../lib64/:/opt/gcc/lib/gcc/x86_64-pc-linux-gnu/11.0.0/../../../:/lib/:/usr/lib/ COLLECT_GCC_OPTIONS='-v' '-save-temps' '-Wall' '-O2' '-gdwarf-5' '-mtune=generic' '-march=x86-64' '-dumpdir' 'a.' /opt/gcc/libexec/gcc/x86_64-pc-linux-gnu/11.0.0/collect2 -plugin /opt/gcc/libexec/gcc/x86_64-pc-linux-gnu/11.0.0/liblto_plugin.so -plugin-opt=/opt/gcc/libexec/gcc/x86_64-pc-linux-gnu/11.0.0/lto-wrapper -plugin-opt=-fresolution=a.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --eh-frame-hdr --hash-style=gnu -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie /lib/../lib64/Scrt1.o /lib/../lib64/crti.o /opt/gcc/lib/gcc/x86_64-pc-linux-gnu/11.0.0/crtbeginS.o -L/opt/gcc/lib/gcc/x86_64-pc-linux-gnu/11.0.0 -L/opt/gcc/lib/gcc/x86_64-pc-linux-gnu/11.0.0/../../../../lib64 -L/lib/../lib64 -L/usr/lib/../lib64 -L/opt/gcc/lib/gcc/x86_64-pc-linux-gnu/11.0.0/../../.. a-main.o -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /opt/gcc/lib/gcc/x86_64-pc-linux-gnu/11.0.0/crtendS.o /lib/../lib64/crtn.o COLLECT_GCC_OPTIONS='-v' '-save-temps' '-Wall' '-O2' '-gdwarf-5' '-mtune=generic' '-march=x86-64' '-dumpdir' 'a.' ``` Using the following command, I can inspect the DWARF output. $ llvm-dwarfdump a.out | less ``` 0x000000fd: DW_TAG_subprogram DW_AT_external (true) DW_AT_name ("call") DW_AT_decl_file ("/data/Workspace/maitrise/analyses/instrumentation-analysis/patches/test/main.c") DW_AT_decl_line (11) DW_AT_decl_column (0x20) DW_AT_prototyped (true) DW_AT_low_pc (0x0000000000001190) DW_AT_high_pc (0x00000000000011c6) DW_AT_frame_base (DW_OP_call_frame_cfa) DW_AT_sibling (0x0000014e) 0x0000011b: DW_TAG_formal_parameter DW_AT_name ("argc") DW_AT_decl_line (11) DW_AT_decl_column (0x29) DW_AT_type (0x00000054 "int") DW_AT_location (0x0000006c: [0x0000000000001190, 0x00000000000011a8): DW_OP_reg5 RDI [0x00000000000011a8, 0x00000000000011aa): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value [0x00000000000011aa, 0x00000000000011b6): DW_OP_reg5 RDI [0x00000000000011b6, 0x00000000000011c6): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value) DW_AT_unknown_2137 (0x00000064) 0x0000012e: DW_TAG_formal_parameter DW_AT_name ("f") DW_AT_decl_file ("/data/Workspace/maitrise/analyses/instrumentation-analysis/patches/test/main.c") DW_AT_decl_line (11) DW_AT_decl_column (0x36) DW_AT_type (0x0000014f "void()*") DW_AT_location (0x00000098: [0x0000000000001190, 0x00000000000011a9): DW_OP_reg4 RSI [0x00000000000011a9, 0x00000000000011aa): DW_OP_entry_value(DW_OP_reg4 RSI), DW_OP_stack_value [0x00000000000011aa, 0x00000000000011c5): DW_OP_reg4 RSI [0x00000000000011c5, 0x00000000000011c6): DW_OP_entry_value(DW_OP_reg4 RSI), DW_OP_stack_value) DW_AT_unknown_2137 (0x00000090) 0x00000140: DW_TAG_call_site DW_AT_call_return_pc (0x00000000000011c6) DW_AT_call_tail_call (true) DW_AT_call_origin (0x0000018c) 0x0000014d: NULL ``` We notice that there is only a single `DW_TAG_call_site` while the function `void call(void)` clearly makes two function calls (one direct, the other through a function pointer). It happens with both GCC-10 and GCC compiled from the git repository. Using clang, I get the following DWARF information. $ clang -Wall -O2 -gdwarf-5 main.c $ readelf --debug-dump=info a.out | less ``` 0x0000003d: DW_TAG_subprogram DW_AT_low_pc (0x0000000000001150) DW_AT_high_pc (0x000000000000118e) DW_AT_frame_base (DW_OP_reg7 RSP) DW_AT_call_all_calls (true) DW_AT_name ("call") DW_AT_decl_file ("/data/Workspace/maitrise/analyses/instrumentation-analysis/patches/test/main.c") DW_AT_decl_line (11) DW_AT_prototyped (true) DW_AT_external (true) 0x00000048: DW_TAG_formal_parameter DW_AT_location (indexed (0x0) loclist = 0x0000001c: [0x0000000000001150, 0x0000000000001185): DW_OP_reg5 RDI) DW_AT_name ("argc") DW_AT_decl_file ("/data/Workspace/maitrise/analyses/instrumentation-analysis/patches/test/main.c") DW_AT_decl_line (11) DW_AT_type (0x000000a2 "int") 0x00000051: DW_TAG_formal_parameter DW_AT_location (indexed (0x1) loclist = 0x00000022: [0x0000000000001150, 0x000000000000118e): DW_OP_reg4 RSI) DW_AT_name ("f") DW_AT_decl_file ("/data/Workspace/maitrise/analyses/instrumentation-analysis/patches/test/main.c") DW_AT_decl_line (11) DW_AT_type (0x000000a6 "void()*") 0x0000005a: DW_TAG_call_site DW_AT_call_origin (0x00000027) DW_AT_call_tail_call (true) DW_AT_call_return_pc (0x000000000000003c) 0x00000067: DW_TAG_call_site DW_AT_call_target (DW_OP_reg4 RSI) DW_AT_call_tail_call (true) DW_AT_call_return_pc (0x000000000000003e) 0x00000072: NULL ``` As you can see, there is two `DW_TAG_call_site` as expected. Is GCC behavior expected? I know DWARF5 support is stil considered experimental, did I miss something in the DWARF5 standard? If it is indeed a bug, I will report it into the bug tracker. I'm also willing to send a patch if someone could give me some pointers. Thanks, Gabriel