diff --git a/debuginfo-tests/CMakeLists.txt b/debuginfo-tests/CMakeLists.txt --- a/debuginfo-tests/CMakeLists.txt +++ b/debuginfo-tests/CMakeLists.txt @@ -19,6 +19,7 @@ llvm-config llvm-objdump check-gdb-llvm-support + llvm-dwarfdump not ) @@ -47,8 +48,7 @@ list(APPEND DEBUGINFO_TEST_DEPS lldb lldb-server) endif() -# The Windows builder scripts pass -fuse-ld=lld. -if (WIN32) +if (NOT APPLE) set(DEBUGINFO_TEST_DEPS ${DEBUGINFO_TEST_DEPS} lld) endif() diff --git a/debuginfo-tests/dwarflinker/Inputs/empty-main.cpp b/debuginfo-tests/dwarflinker/Inputs/empty-main.cpp new file mode 100644 --- /dev/null +++ b/debuginfo-tests/dwarflinker/Inputs/empty-main.cpp @@ -0,0 +1,3 @@ +int main ( void ) { + return 0; +} diff --git a/debuginfo-tests/dwarflinker/Inputs/full-main.cpp b/debuginfo-tests/dwarflinker/Inputs/full-main.cpp new file mode 100644 --- /dev/null +++ b/debuginfo-tests/dwarflinker/Inputs/full-main.cpp @@ -0,0 +1,13 @@ +#include +int mod1_f1(int); +int mod2_f1(const char*); +int mod1_not_used(int p1); +int mod2_not_used(int p1); +int main ( void ) { + int res = mod1_f1(1); + res += mod2_f1("hello"); + res += mod1_not_used(res); + res += mod2_not_used(res); + printf("\n %d", res); + return 0; +} diff --git a/debuginfo-tests/dwarflinker/Inputs/gc-debuginfo-main-lto.cpp b/debuginfo-tests/dwarflinker/Inputs/gc-debuginfo-main-lto.cpp new file mode 100644 --- /dev/null +++ b/debuginfo-tests/dwarflinker/Inputs/gc-debuginfo-main-lto.cpp @@ -0,0 +1,16 @@ +#include + +int mod1_f1(int); +int mod2_f1(const char*); + +int main(void) { + int v1 = mod1_f1(3); + int v2 = mod2_f1("hello"); + + for (int idx = 0; idx < 3; idx++) { + printf("%d", v1+idx); + printf("%d", v2+idx); + } + + return 0; +} diff --git a/debuginfo-tests/dwarflinker/Inputs/gc-debuginfo-main.c b/debuginfo-tests/dwarflinker/Inputs/gc-debuginfo-main.c new file mode 100644 --- /dev/null +++ b/debuginfo-tests/dwarflinker/Inputs/gc-debuginfo-main.c @@ -0,0 +1,18 @@ +#include + +int mod1_f1(int); +int mod2_f1(int); + +__attribute__((optnone)) void func(int p) { + printf("\n%d", p); +} + +__attribute__((optnone)) int main(void) { + int v1 = mod1_f1(3); + int v2 = mod2_f1(5); + + for (int idx = 0; idx < 3; idx++) + func(v1 * idx + v2); + + return 0; +} diff --git a/debuginfo-tests/dwarflinker/Inputs/gc-debuginfo-mod1-lto.cpp b/debuginfo-tests/dwarflinker/Inputs/gc-debuginfo-mod1-lto.cpp new file mode 100644 --- /dev/null +++ b/debuginfo-tests/dwarflinker/Inputs/gc-debuginfo-mod1-lto.cpp @@ -0,0 +1,5 @@ +__attribute__((optnone)) int mod1_f1(int p1) { return p1 + 10; } + +__attribute__((optnone)) int mod1_not_used(int p1) { + return p1+100; +} diff --git a/debuginfo-tests/dwarflinker/Inputs/gc-debuginfo-mod1.c b/debuginfo-tests/dwarflinker/Inputs/gc-debuginfo-mod1.c new file mode 100644 --- /dev/null +++ b/debuginfo-tests/dwarflinker/Inputs/gc-debuginfo-mod1.c @@ -0,0 +1,8 @@ +int mod1_f1(int p1) { return p1 + 10; } + +int mod1_not_used(int p1) { + int r = 0; + for (int i = 0; i < p1; i++) + r++; + return r; +} diff --git a/debuginfo-tests/dwarflinker/Inputs/gc-debuginfo-mod2-lto.cpp b/debuginfo-tests/dwarflinker/Inputs/gc-debuginfo-mod2-lto.cpp new file mode 100644 --- /dev/null +++ b/debuginfo-tests/dwarflinker/Inputs/gc-debuginfo-mod2-lto.cpp @@ -0,0 +1,23 @@ + +class S { + public: +__attribute__((optnone)) S(const char* str) { + l = 0; + while (!*str) + l++; + } + +__attribute__((optnone)) int Length() { + return l; + } + + int l = 0; +}; + +__attribute__((optnone)) int mod2_f1(const char* n) { + return S(n).Length(); +} + +__attribute__((optnone)) int mod2_not_used ( int p1) { + return p1+100; +} diff --git a/debuginfo-tests/dwarflinker/Inputs/gc-debuginfo-mod2.c b/debuginfo-tests/dwarflinker/Inputs/gc-debuginfo-mod2.c new file mode 100644 --- /dev/null +++ b/debuginfo-tests/dwarflinker/Inputs/gc-debuginfo-mod2.c @@ -0,0 +1,10 @@ + +int mod2_f1(int p1) { return p1 + 10; } + +int mod2_not_used(int p1) { + int r = 0; + for (int i = 0; i < p1; i++) + r++; + + return r; +} diff --git a/debuginfo-tests/dwarflinker/Inputs/odr-foo.cpp b/debuginfo-tests/dwarflinker/Inputs/odr-foo.cpp new file mode 100644 --- /dev/null +++ b/debuginfo-tests/dwarflinker/Inputs/odr-foo.cpp @@ -0,0 +1,5 @@ +#include "struct.h" + +__attribute__((optnone)) int foo ( S p ) { + return p.a1 + p.a2; +} diff --git a/debuginfo-tests/dwarflinker/Inputs/odr-main.cpp b/debuginfo-tests/dwarflinker/Inputs/odr-main.cpp new file mode 100644 --- /dev/null +++ b/debuginfo-tests/dwarflinker/Inputs/odr-main.cpp @@ -0,0 +1,8 @@ +#include "struct.h" +int foo(S); + +int main ( void ) { + S s; s.a1 = 2; s.a2 = 5; + + return foo(s); +} diff --git a/debuginfo-tests/dwarflinker/Inputs/struct.h b/debuginfo-tests/dwarflinker/Inputs/struct.h new file mode 100644 --- /dev/null +++ b/debuginfo-tests/dwarflinker/Inputs/struct.h @@ -0,0 +1,4 @@ +struct S { + int a1; + int a2; +}; diff --git a/debuginfo-tests/dwarflinker/empty.cpp b/debuginfo-tests/dwarflinker/empty.cpp new file mode 100644 --- /dev/null +++ b/debuginfo-tests/dwarflinker/empty.cpp @@ -0,0 +1,44 @@ +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O %p/Inputs/empty-main.cpp -c -o %t.o +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O %p/Inputs/gc-debuginfo-mod1-lto.cpp -c -o %t1.o +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O %p/Inputs/gc-debuginfo-mod2-lto.cpp -c -o %t2.o +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -O -fuse-ld=lld \ +// RUN: -Wl,--gc-sections,--gc-debuginfo %t.o %t1.o %t2.o -o %t3.out +// RUN: llvm-dwarfdump -a %t3.out | FileCheck %s +// RUN: llvm-dwarfdump --verify %t3.out | FileCheck %s --check-prefix=VERIFY + +// UNSUPPORTED: system-darwin + +// This test checks that --gc-debuginfo removes unused debug info. +// It checks that debug info related to all functions from +// gc-debuginfo-mod1-lto.cpp and gc-debuginfo-mod2-lto.cpp is not +// put into resulting binary + +CHECK: .debug_info contents: +CHECK: DW_TAG_compile_unit +CHECK: DW_AT_name ("{{.*}}empty-main.cpp") +CHECK: DW_AT_stmt_list (0x[[MAIN_STMT_LIST:[0-9A-Fa-f]+]]) +CHECK: DW_AT_low_pc (0x[[LOWPC_ADDR:[0-9A-Fa-f]+]]) +CHECK: DW_AT_high_pc (0x[[HIGHPC_ADDR:[0-9A-Fa-f]+]]) +CHECK: DW_TAG_subprogram +CHECK: DW_AT_low_pc (0x[[LOWPC_ADDR]]) +CHECK: DW_AT_high_pc (0x[[HIGHPC_ADDR]]) +CHECK: DW_AT_name ("main") +CHECK-NOT: DW_TAG_subprogram +CHECK: NULL +CHECK: .eh_frame contents: +CHECK: .debug_line contents: +CHECK: debug_line[0x[[MAIN_STMT_LIST]] +CHECK: Address +CHECK: 0x{{0*}}[[LOWPC_ADDR]] +CHECK: 0x{{0*}}[[HIGHPC_ADDR]] +CHECK-NOT: mod1_f1 +CHECK-NOT: mod2_f1 +CHECK-NOT: mod1_not_used +CHECK-NOT: mod2_not_used +CHECK-NOT: .debug_ranges contents: + +VERIFY: Verifying +VERIFY: No errors. diff --git a/debuginfo-tests/dwarflinker/full.cpp b/debuginfo-tests/dwarflinker/full.cpp new file mode 100644 --- /dev/null +++ b/debuginfo-tests/dwarflinker/full.cpp @@ -0,0 +1,136 @@ +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O %p/Inputs/full-main.cpp -c -o %t.o +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O %p/Inputs/gc-debuginfo-mod1-lto.cpp -c -o %t1.o +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O %p/Inputs/gc-debuginfo-mod2-lto.cpp -c -o %t2.o +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -O -fuse-ld=lld \ +// RUN: -Wl,--gc-sections,--gc-debuginfo %t.o %t1.o %t2.o -o %t3.out +// RUN: llvm-dwarfdump -a %t3.out | FileCheck %s +// RUN: llvm-dwarfdump --verify %t3.out | FileCheck %s --check-prefix=VERIFY + +// UNSUPPORTED: system-darwin + +// This test checks that --gc-debuginfo does not remove debug info. +// (Because there is no code deleted by --gc-sections). +// It checks that debug info related to all functions from +// gc-debuginfo-mod1-lto.cpp and gc-debuginfo-mod2-lto.cpp is put +// into resulting binary + +CHECK: .debug_info contents: +CHECK: DW_TAG_compile_unit +CHECK: DW_AT_name ("{{.*}}full-main.cpp") +CHECK: DW_AT_stmt_list (0x[[MAIN_STMT_LIST:[0-9A-Fa-f]+]]) +CHECK: DW_AT_low_pc (0x[[LOWPC_ADDR:[0-9A-Fa-f]+]]) +CHECK: DW_AT_high_pc (0x[[HIGHPC_ADDR:[0-9A-Fa-f]+]]) +CHECK: DW_TAG_subprogram +CHECK: DW_AT_low_pc (0x[[LOWPC_ADDR]]) +CHECK: DW_AT_high_pc (0x[[HIGHPC_ADDR]]) +CHECK: DW_AT_name ("main") +CHECK: DW_TAG_variable +CHECK: DW_AT_location +CHECK: DW_AT_name ("res") +CHECK: DW_TAG_subprogram +CHECK: DW_AT_name ("mod1_f1") +CHECK: DW_TAG_formal_parameter +CHECK: DW_TAG_subprogram +CHECK: DW_AT_name ("mod2_f1") +CHECK: DW_TAG_formal_parameter +CHECK: DW_TAG_subprogram +CHECK: DW_AT_name ("mod1_not_used") +CHECK: DW_TAG_formal_parameter +CHECK: DW_TAG_subprogram +CHECK: DW_AT_name ("mod2_not_used") +CHECK: DW_TAG_formal_parameter +CHECK: NULL +CHECK: NULL + +CHECK: DW_TAG_compile_unit +CHECK: DW_AT_name ("{{.*}}gc-debuginfo-mod1-lto.cpp") +CHECK: DW_AT_stmt_list (0x[[MOD1_STMT_LIST:[0-9A-Fa-f]+]]) +CHECK: DW_AT_ranges +CHECK: [0x{{0*}}[[MOD1_ADDR:[0-9A-Fa-f]+]], +CHECK: [0x{{0*}}[[MOD1_NON_USED_ADDR:[0-9A-Fa-f]+]], +CHECK: DW_TAG_subprogram +CHECK: DW_AT_low_pc (0x{{0*}}[[MOD1_ADDR]] +CHECK: DW_AT_name ("mod1_f1") +CHECK: DW_TAG_subprogram +CHECK: DW_AT_low_pc (0x{{0*}}[[MOD1_NON_USED_ADDR]] +CHECK: DW_AT_name ("mod1_not_used") +CHECK: NULL +CHECK: NULL + +CHECK: DW_TAG_compile_unit +CHECK: DW_AT_name ("{{.*}}gc-debuginfo-mod2-lto.cpp") +CHECK: DW_AT_stmt_list (0x[[MOD2_STMT_LIST:[0-9A-Fa-f]+]]) +CHECK: DW_AT_ranges +CHECK: [0x{{0*}}[[MOD2_ADDR:[0-9A-Fa-f]+]], +CHECK: [0x{{0*}}[[MOD2_S_ADDR:[0-9A-Fa-f]+]], +CHECK: [0x{{0*}}[[MOD2_LENGTH_ADDR:[0-9A-Fa-f]+]], +CHECK: [0x{{0*}}[[MOD2_NON_USED_ADDR:[0-9A-Fa-f]+]], +CHECK: DW_TAG_class_type +CHECK: DW_AT_name ("S") +CHECK: DW_TAG_member +CHECK: DW_AT_name ("l") +CHECK: DW_TAG_subprogram +CHECK: DW_AT_name ("S") +CHECK: DW_TAG_subprogram +CHECK: DW_AT_name ("Length") +CHECK: NULL +CHECK: NULL +CHECK: DW_TAG_subprogram +CHECK: DW_AT_low_pc (0x{{0*}}[[MOD2_ADDR]] +CHECK: DW_AT_name ("mod2_f1") +CHECK: DW_TAG_formal_parameter +CHECK: DW_AT_name ("n") +CHECK: DW_TAG_subprogram +CHECK: DW_AT_low_pc (0x{{0*}}[[MOD2_S_ADDR]] +CHECK: DW_TAG_formal_parameter +CHECK: DW_AT_name ("this") +CHECK: DW_TAG_formal_parameter +CHECK: DW_AT_name ("str") +CHECK: DW_TAG_subprogram +CHECK: DW_AT_low_pc (0x{{0*}}[[MOD2_LENGTH_ADDR]] +CHECK: DW_TAG_formal_parameter +CHECK: DW_AT_name ("this") +CHECK: DW_TAG_subprogram +CHECK: DW_AT_low_pc (0x{{0*}}[[MOD2_NON_USED_ADDR]] +CHECK: DW_TAG_formal_parameter +CHECK: DW_AT_name ("p1") +CHECK: NULL + +CHECK: .debug_loc contents: + +CHECK: .eh_frame contents: + +CHECK: .debug_line contents: +CHECK: debug_line[0x[[MAIN_STMT_LIST]]] +CHECK: Address +CHECK: 0x{{0*}}[[LOWPC_ADDR]] +CHECK: debug_line[0x[[MOD1_STMT_LIST]]] +CHECK: Address +CHECK: 0x{{0*}}[[MOD1_ADDR]] +CHECK: 0x{{0*}}[[MOD1_NON_USED_ADDR]] +CHECK: debug_line[0x[[MOD2_STMT_LIST]]] +CHECK: Address +CHECK: 0x{{0*}}[[MOD2_ADDR]] +CHECK: 0x{{0*}}[[MOD2_S_ADDR]] +CHECK: 0x{{0*}}[[MOD2_LENGTH_ADDR]] +CHECK: 0x{{0*}}[[MOD2_NON_USED_ADDR]] + +CHECK: .debug_str contents: +CHECK: "main" +CHECK: "res" +CHECK: "mod1_f1" +CHECK: "mod2_f1" +CHECK: "mod1_not_used" +CHECK: "mod2_not_used" +CHECK: "S" +CHECK: "Length" +CHECK: "this" + +CHECK: .debug_ranges contents: + + +VERIFY: Verifying +VERIFY: No errors. diff --git a/debuginfo-tests/dwarflinker/gc-debuginfo-lto.cpp b/debuginfo-tests/dwarflinker/gc-debuginfo-lto.cpp new file mode 100644 --- /dev/null +++ b/debuginfo-tests/dwarflinker/gc-debuginfo-lto.cpp @@ -0,0 +1,96 @@ +// RUN: %clang++ %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O -flto %p/Inputs/gc-debuginfo-main-lto.cpp -c -o %t.o +// RUN: %clang++ %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O -flto %p/Inputs/gc-debuginfo-mod1-lto.cpp -c -o %t1.o +// RUN: %clang++ %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O -flto %p/Inputs/gc-debuginfo-mod2-lto.cpp -c -o %t2.o +// RUN: %clang++ %target_itanium_abi_host_triple -gdwarf-4 -O -fuse-ld=lld \ +// RUN: -flto -Wl,--gc-sections,--gc-debuginfo %t.o %t1.o %t2.o -o %t3.out +// RUN: llvm-dwarfdump -a %t3.out | FileCheck %s +// RUN: llvm-dwarfdump --verify %t3.out | FileCheck %s --check-prefix=VERIFY + +// UNSUPPORTED: system-darwin + +// This test checks that --gc-debuginfo removes unused debug info +// correctly in case -flto. After -flto there would be single .debug_info +// section containing all compile units. The test checks that dwarflinker +// correctly processes several compilation units placed into single +// .debug info section. The test checks that debug info related +// to mod1_not_used() and mod2_not_used() functions +// is deleted from resulting .debug* tables. + +CHECK: .debug_info contents: +CHECK: 0x0000000b: DW_TAG_compile_unit +CHECK: DW_AT_name ("{{.*}}gc-debuginfo-main-lto.cpp") +CHECK: DW_AT_stmt_list (0x[[MAIN_STMT_LIST:[0-9A-Fa-f]+]]) +CHECK: DW_AT_low_pc (0x{{0*}}[[MAIN_LPC:[0-9A-Fa-f]+]]) +CHECK: DW_AT_high_pc (0x{{0*}}[[MAIN_HPC:[0-9A-Fa-f]+]]) +CHECK: DW_TAG_subprogram +CHECK: DW_AT_low_pc (0x{{0*}}[[MAIN_LPC]]) +CHECK: DW_AT_high_pc (0x{{0*}}[[MAIN_HPC]]) +CHECK: DW_AT_name ("main") +CHECK: DW_TAG_variable +CHECK: DW_AT_location +CHECK: DW_AT_name ("v1") +CHECK: DW_TAG_variable +CHECK: DW_AT_location +CHECK: DW_AT_name ("v2") +CHECK: DW_TAG_variable +CHECK: DW_AT_location +CHECK: DW_AT_name ("idx") +CHECK: DW_TAG_GNU_call_site +CHECK: DW_AT_low_pc (0x{{0*}}[[MOD1_VAL:[0-9A-Fa-f]+]]) +CHECK: DW_TAG_GNU_call_site +CHECK: DW_AT_low_pc (0x{{0*}}[[MOD2_VAL:[0-9A-Fa-f]+]]) +CHECK: Compile Unit +CHECK_NOT: 0x0000000b +CHECK: DW_TAG_compile_unit +CHECK: DW_AT_name ("{{.*}}gc-debuginfo-mod2-lto.cpp") +CHECK: DW_AT_stmt_list (0x{{0*}}[[MOD2_STMT_LIST:[0-9A-Fa-f]+]]) +CHECK: DW_AT_ranges (0x{{0*}}[[RANGE_LIST_MOD2:[0-9A-Fa-f]+]] +CHECK-NEXT: 0x{{0*}}[[MOD2_VAL_ADDR:[0-9A-Fa-f]+]], 0x{{[0-9a-zA-Z]*}} +CHECK-NEXT: 0x{{0*}}[[MOD2_LENGTH_ADDR:[0-9A-Fa-f]+]], 0x{{[0-9a-zA-Z]*}} +CHECK-NEXT: 0x{{0*}}[[MOD2_CONSTR_ADDR:[0-9A-Fa-f]+]], 0x{{[0-9a-zA-Z]*}} +CHECK: DW_TAG_class_type +CHECK: DW_AT_name ("S") +CHECK: DW_TAG_member +CHECK: DW_AT_name ("l") +CHECK: DW_TAG_subprogram +CHECK: DW_AT_name ("S") +CHECK: DW_TAG_subprogram +CHECK: DW_AT_name ("Length") +CHECK: DW_TAG_subprogram +CHECK: DW_AT_low_pc (0x{{0*}}[[MOD2_VAL_ADDR]]) +CHECK: DW_AT_name ("mod2_f1") +CHECK: DW_TAG_subprogram +CHECK: DW_AT_low_pc (0x{{0*}}[[MOD2_LENGTH_ADDR]]) +CHECK: DW_TAG_subprogram +CHECK: DW_AT_low_pc (0x{{0*}}[[MOD2_CONSTR_ADDR]]) +CHECK: Compile Unit +CHECK_NOT: 0x0000000b +CHECK: DW_TAG_compile_unit +CHECK: DW_AT_name ("{{.*}}gc-debuginfo-mod1-lto.cpp") +CHECK: DW_AT_stmt_list (0x{{0*}}[[MOD1_STMT_LIST:[0-9A-Fa-f]+]]) +CHECK: DW_AT_low_pc (0x{{0*}}[[MOD1_LPC:[0-9A-Fa-f]+]]) +CHECK: DW_AT_high_pc (0x{{0*}}[[MOD1_HPC:[0-9A-Fa-f]+]]) +CHECK: .debug_loc contents +CHECK: .debug_line contents: +CHECK: debug_line[0x[[MAIN_STMT_LIST]]] +CHECK: Address +CHECK: 0x{{0*}}[[MAIN_LPC]] +CHECK: debug_line[0x{{0*}}[[MOD2_STMT_LIST]]] +CHECK: Address +CHECK: 0x{{0*}}[[MOD2_VAL_ADDR]] +CHECK: 0x{{0*}}[[MOD2_LENGTH_ADDR]] +CHECK: 0x{{0*}}[[MOD2_CONSTR_ADDR]] +CHECK: debug_line[0x{{0*}}[[MOD1_STMT_LIST]]] +CHECK: Address +CHECK: 0x{{0*}}[[MOD1_LPC]] + +CHECK: .debug_str contents +CHECK-NOT: mod1_not_used +CHECK-NOT: mod2_not_used +CHECK: .debug_ranges contents: + +VERIFY: Verifying +VERIFY: No errors. diff --git a/debuginfo-tests/dwarflinker/gc-debuginfo.c b/debuginfo-tests/dwarflinker/gc-debuginfo.c new file mode 100644 --- /dev/null +++ b/debuginfo-tests/dwarflinker/gc-debuginfo.c @@ -0,0 +1,96 @@ +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -fno-inline -O %p/Inputs/gc-debuginfo-main.c -c -o %t.o +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -fno-inline -O %p/Inputs/gc-debuginfo-mod1.c -c -o %t1.o +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -fno-inline -O %p/Inputs/gc-debuginfo-mod2.c -c -o %t2.o +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -O -fuse-ld=lld \ +// RUN: -Wl,--gc-sections,--gc-debuginfo %t.o %t1.o %t2.o -o %t3.out +// RUN: llvm-dwarfdump -a %t3.out | FileCheck %s +// RUN: llvm-dwarfdump --verify %t3.out | FileCheck %s --check-prefix=VERIFY + +// UNSUPPORTED: system-darwin + +// This test checks that --gc-debuginfo removes unused debug info. +// It checks that debug info related to mod1_not_used() and +// mod2_not_used() functions is deleted from resulting .debug* +// tables. .debug_loc table should be empty since mod1_f1() +// and mod2_f1() functions do not refer to .debug_loc. + +CHECK: .debug_info contents: +CHECK: DW_TAG_compile_unit +CHECK: DW_AT_name ("{{.*}}gc-debuginfo-main.c") +CHECK: DW_AT_stmt_list (0x[[MAIN_STMT_LIST:[0-9A-Fa-f]+]]) +CHECK: DW_AT_ranges (0x00000000 +CHECK-NEXT: [0x{{0*}}[[MAIN_ADDR:[0-9A-Fa-f]+]], 0x{{[0-9a-zA-Z]*}}) +CHECK: DW_TAG_subprogram +CHECK: DW_AT_low_pc (0x{{0*}}[[MAIN_ADDR]]) +CHECK-NOT: mod1_not_used +CHECK-NOT: mod2_not_used +CHECK: DW_AT_name ("main") +CHECK: DW_TAG_subprogram +CHECK-NOT: mod1_not_used +CHECK-NOT: mod2_not_used +CHECK: DW_AT_name ("mod1_f1") +CHECK: DW_TAG_subprogram +CHECK-NOT: mod1_not_used +CHECK-NOT: mod2_not_used +CHECK: DW_AT_name ("mod2_f1") +CHECK: Compile Unit +CHECK: DW_TAG_compile_unit +CHECK-NOT: mod1_not_used +CHECK-NOT: mod2_not_used +CHECK: DW_AT_name ("{{.*}}gc-debuginfo-mod1.c") +CHECK: DW_AT_stmt_list (0x{{0*}}[[MOD1_STMT_LIST:[0-9A-Fa-f]+]]) +CHECK: DW_AT_ranges (0x{{0*}}[[RANGE_LIST_MOD1:[0-9A-Fa-f]+]] +CHECK-NEXT: [0x{{0*}}[[GET_MOD1_ADDR:[0-9A-Fa-f]+]], 0x{{[0-9a-zA-Z]*}})) +CHECK: DW_TAG_subprogram +CHECK: DW_AT_low_pc (0x{{0*}}[[GET_MOD1_ADDR]] +CHECK-NOT: mod1_not_used +CHECK-NOT: mod2_not_used +CHECK: DW_AT_name ("mod1_f1") +CHECK: Compile Unit +CHECK: DW_TAG_compile_unit +CHECK-NOT: mod1_not_used +CHECK-NOT: mod2_not_used +CHECK: DW_AT_name ("{{.*}}gc-debuginfo-mod2.c") +CHECK: DW_AT_stmt_list (0x{{0*}}[[MOD2_STMT_LIST:[0-9A-Fa-f]+]]) +CHECK: DW_AT_ranges (0x{{0*}}[[RANGE_LIST_MOD2:[0-9A-Fa-f]+]] +CHECK-NEXT: [0x[[GET_MOD2_ADDR:[0-9A-Fa-f]+]], 0x{{[0-9a-zA-Z]*}})) +CHECK: DW_TAG_subprogram +CHECK: DW_AT_low_pc (0x{{0*}}[[GET_MOD2_ADDR]] +CHECK-NOT: mod1_not_used +CHECK-NOT: mod2_not_used +CHECK: DW_AT_name ("mod2_f1") +CHECK-NOT: mod1_not_used +CHECK-NOT: mod2_not_used +CHECK: NULL + +CHECK-NOT: .debug_loc contents: + +CHECK: .debug_line contents: +CHECK: debug_line[0x[[MAIN_STMT_LIST]]] +CHECK: Address +CHECK: 0x{{0*}}[[MAIN_ADDR:[0-9A-Fa-f]+]] +CHECK: debug_line[0x{{0*}}[[MOD1_STMT_LIST]]] +CHECK: Address +CHECK: 0x{{0*}}[[GET_MOD1_ADDR:[0-9A-Fa-f]+]] +CHECK: debug_line[0x{{0*}}[[MOD2_STMT_LIST]]] +CHECK: Address +CHECK: 0x{{0*}}[[GET_MOD2_ADDR:[0-9A-Fa-f]+]] + + +CHECK: .debug_str contents: +CHECK-NOT: mod1_not_used +CHECK-NOT: mod2_not_used + +CHECK: .debug_ranges contents: +CHECK: 00000000 0000000000000000 {{[0-9a-zA-Z]*}} +CHECK: 00000000 +CHECK: {{0*}}[[RANGE_LIST_MOD1]] 0000000000000000 {{[0-9a-zA-Z]*}} +CHECK: {{0*}}[[RANGE_LIST_MOD1]] +CHECK: {{0*}}[[RANGE_LIST_MOD2]] 0000000000000000 {{[0-9a-zA-Z]*}} +CHECK: {{0*}}[[RANGE_LIST_MOD2]] + +VERIFY: Verifying +VERIFY: No errors. diff --git a/debuginfo-tests/dwarflinker/odr.cpp b/debuginfo-tests/dwarflinker/odr.cpp new file mode 100644 --- /dev/null +++ b/debuginfo-tests/dwarflinker/odr.cpp @@ -0,0 +1,48 @@ +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -fno-inline -O %p/Inputs/odr-main.cpp -c -o %t.o +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -fno-inline -O %p/Inputs/odr-foo.cpp -c -o %t1.o +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -O -fuse-ld=lld \ +// RUN: -Wl,--gc-sections,--gc-debuginfo %t.o %t1.o -o %t2.out +// RUN: llvm-dwarfdump -a %t2.out | FileCheck %s +// RUN: llvm-dwarfdump --verify %t2.out | FileCheck %s --check-prefix=VERIFY + +// UNSUPPORTED: system-darwin + +// This test checks that --gc-debuginfo properly implements +// One Definition Rule. It checks that debug info related to struct S +// presented only once(in first compilation unit). And that second +// compilation unit references definition from the first compilation unit. + +CHECK: .debug_info contents: +CHECK: DW_TAG_compile_unit +CHECK: DW_AT_name ("{{.*}}odr-main.cpp") +CHECK: DW_TAG_subprogram +CHECK: DW_AT_name ("main") +CHECK: DW_TAG_variable +CHECK: DW_AT_name ("s") +CHECK: DW_AT_type (0x{{0*}}[[STRUCT_OFFSET:[0-9A-Fa-f]+]] +CHECK: NULL +CHECK: NULL +CHECK: DW_TAG_subprogram +CHECK: DW_AT_name ("foo") +CHECK: DW_TAG_formal_parameter +CHECK: DW_AT_type (0x{{0*}}[[STRUCT_OFFSET]] +CHECK: 0x{{0*}}[[STRUCT_OFFSET:[0-9A-Fa-f]+]]: DW_TAG_structure_type +CHECK: DW_AT_name ("S") +CHECK: DW_TAG_member +CHECK: DW_AT_name ("a1") +CHECK: DW_AT_name ("a2") +CHECK: NULL +CHECK: NULL +CHECK: DW_TAG_compile_unit +CHECK: DW_AT_name ("{{.*}}odr-foo.cpp") +CHECK: DW_TAG_subprogram +CHECK: DW_AT_name ("foo") +CHECK: DW_TAG_formal_parameter +CHECK: DW_AT_name ("p") +CHECK: DW_AT_type (0x{{0*}}[[STRUCT_OFFSET]] +CHECK: NULL + +VERIFY: Verifying +VERIFY: No errors. diff --git a/debuginfo-tests/llgdb-tests/dwarflinker/Inputs/debuginfo-modules.h b/debuginfo-tests/llgdb-tests/dwarflinker/Inputs/debuginfo-modules.h new file mode 100644 --- /dev/null +++ b/debuginfo-tests/llgdb-tests/dwarflinker/Inputs/debuginfo-modules.h @@ -0,0 +1,9 @@ +struct Foo { + int x, y; +}; +inline int foo() { + volatile Foo var; + var.x = 13; + var.y = 42; + return var.x + var.y; +} diff --git a/debuginfo-tests/llgdb-tests/dwarflinker/Inputs/debuginfo-modules.modulemap b/debuginfo-tests/llgdb-tests/dwarflinker/Inputs/debuginfo-modules.modulemap new file mode 100644 --- /dev/null +++ b/debuginfo-tests/llgdb-tests/dwarflinker/Inputs/debuginfo-modules.modulemap @@ -0,0 +1,3 @@ +module foo { +header "debuginfo-modules.h" +} diff --git a/debuginfo-tests/llgdb-tests/dwarflinker/Inputs/gc-debuginfo-mod1-lto.cpp b/debuginfo-tests/llgdb-tests/dwarflinker/Inputs/gc-debuginfo-mod1-lto.cpp new file mode 100644 --- /dev/null +++ b/debuginfo-tests/llgdb-tests/dwarflinker/Inputs/gc-debuginfo-mod1-lto.cpp @@ -0,0 +1,9 @@ +struct S { + int a1; + int a2; +}; +int mod1_func(void) { + volatile S var; + var.a1 = 100; var.a2 = 200; + return var.a1 + var.a2; +} diff --git a/debuginfo-tests/llgdb-tests/dwarflinker/Inputs/gc-debuginfo-mod1.c b/debuginfo-tests/llgdb-tests/dwarflinker/Inputs/gc-debuginfo-mod1.c new file mode 100644 --- /dev/null +++ b/debuginfo-tests/llgdb-tests/dwarflinker/Inputs/gc-debuginfo-mod1.c @@ -0,0 +1,9 @@ +int mod1_f1(int p1) { + int r = 0; + for (int i = 0; i < p1; i++) + r++; + + return r; +} + +int mod1_not_used(int p1) { return p1 + 10; } diff --git a/debuginfo-tests/llgdb-tests/dwarflinker/Inputs/gc-debuginfo-mod2.c b/debuginfo-tests/llgdb-tests/dwarflinker/Inputs/gc-debuginfo-mod2.c new file mode 100644 --- /dev/null +++ b/debuginfo-tests/llgdb-tests/dwarflinker/Inputs/gc-debuginfo-mod2.c @@ -0,0 +1,9 @@ +int mod2_f1(int p1) { + int r = 0; + for (int i = 0; i < p1; i++) + r += p1; + + return r; +} + +int mod2_not_used(int p1) { return p1 + 10; } diff --git a/debuginfo-tests/llgdb-tests/dwarflinker/Inputs/inline-foo.c b/debuginfo-tests/llgdb-tests/dwarflinker/Inputs/inline-foo.c new file mode 100644 --- /dev/null +++ b/debuginfo-tests/llgdb-tests/dwarflinker/Inputs/inline-foo.c @@ -0,0 +1,9 @@ +#include +int foo(int p1) { + int r = 10; + for (int i = 0; i < p1; i++) { + r++; + printf("\n r %d i %d p1 %d", r, i, p1 ); + } + return r; +} diff --git a/debuginfo-tests/llgdb-tests/dwarflinker/debuginfo-modules.cpp b/debuginfo-tests/llgdb-tests/dwarflinker/debuginfo-modules.cpp new file mode 100644 --- /dev/null +++ b/debuginfo-tests/llgdb-tests/dwarflinker/debuginfo-modules.cpp @@ -0,0 +1,32 @@ +// RUN: %clang -cc1 -debug-info-kind=limited -dwarf-version=4 -fmodules-debuginfo \ +// RUN: -x c++ -fmodules -emit-module -fmodule-name=foo \ +// RUN: %p/Inputs/debuginfo-modules.modulemap -o %t.pch +// RUN: %clang -cc1 -debug-info-kind=limited -dwarf-version=4 %s \ +// RUN: -fmodules -fmodule-file=%t.pch -emit-obj -o %t2.o +// RUN: %clang -cc1 -debug-info-kind=limited -dwarf-version=4 \ +// RUN: -x ast %t.pch -fmodules -emit-obj -o %t3.o +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -O1 -fno-inline \ +// RUN: -fuse-ld=lld -Wl,--gc-sections,--gc-debuginfo %t2.o %t3.o -o %t3.out +// RUN: %test_debuginfo %s %t3.out + +//XFAIL:* + +// UNSUPPORTED: system-darwin + +// This test checks that debug information for -fmodules-debuginfo is +// valid after --gc-debuginfo done. It generates debuginfo module %t.pch +// , object file for debuginfo-modules.cpp and object file for %t.pch +// and finally links output. Resulting output should contain debug info from +// %t.pch. i.e. "p var" debbugger command should print proper values +// (x = 13, y = 42) + +#include "Inputs/debuginfo-modules.h" +int main() { return foo(); } + +// DEBUGGER: break foo +// CHECK: foo () +// DEBUGGER: r +// DEBUGGER: n +// DEBUGGER: n +// DEBUGGER: p var +// CHECK: x = 13, y = 42 diff --git a/debuginfo-tests/llgdb-tests/dwarflinker/gc-debuginfo-lto.cpp b/debuginfo-tests/llgdb-tests/dwarflinker/gc-debuginfo-lto.cpp new file mode 100644 --- /dev/null +++ b/debuginfo-tests/llgdb-tests/dwarflinker/gc-debuginfo-lto.cpp @@ -0,0 +1,40 @@ +// RUN: %clang++ %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O2 %s -c -o %t.o +// RUN: %clang++ %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O2 %p/Inputs/gc-debuginfo-mod1-lto.cpp -c -o %t1.o +// RUN: %clang++ %target_itanium_abi_host_triple -gdwarf-4 -O2 \ +// RUN: -fuse-ld=lld -Wl,--gc-sections,--gc-debuginfo %t.o %t1.o -o %t3.out +// RUN: %test_debuginfo %s %t3.out +// RUN: %clang++ %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O2 -flto %s -c -o %t.o +// RUN: %clang++ %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O2 -flto %p/Inputs/gc-debuginfo-mod1-lto.cpp -c -o %t1.o +// RUN: %clang++ %target_itanium_abi_host_triple -gdwarf-4 -O2 -flto \ +// RUN: -fuse-ld=lld -Wl,--gc-sections,--gc-debuginfo %t.o %t1.o -o %t3.out +// RUN: %test_debuginfo %s %t3.out +// RUN: %clang++ %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O2 -flto=thin %s -c -o %t.o +// RUN: %clang++ %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O2 -flto=thin %p/Inputs/gc-debuginfo-mod1-lto.cpp -c -o %t1.o +// RUN: %clang++ %target_itanium_abi_host_triple -gdwarf-4 -O2 -flto=thin \ +// RUN: -fuse-ld=lld -Wl,--gc-sections,--gc-debuginfo %t.o %t1.o -o %t3.out +// RUN: %test_debuginfo %s %t3.out + +// XFAIL:* + +// UNSUPPORTED: system-darwin + +// This test checks that debug info for mod1_func() function is properly +// preserved. Specifically, it should print proper values for var variable. +// Check that non-lto, full-lto, thin-lto modes are work correctly. + +int mod1_func(void); +int main(void) { + return mod1_func(); +} + +// DEBUGGER: b gc-debuginfo-mod1-lto.cpp:8 +// DEBUGGER: r +// DEBUGGER: p var +// CHECK: return var.a1 + var.a2 +// CHECK: a1 = 100, a2 = 200 diff --git a/debuginfo-tests/llgdb-tests/dwarflinker/gc-debuginfo.c b/debuginfo-tests/llgdb-tests/dwarflinker/gc-debuginfo.c new file mode 100644 --- /dev/null +++ b/debuginfo-tests/llgdb-tests/dwarflinker/gc-debuginfo.c @@ -0,0 +1,50 @@ +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O1 -fno-inline %s -c -o %t.o +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O1 -fno-inline %p/Inputs/gc-debuginfo-mod1.c -c -o %t1.o +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O1 -fno-inline %p/Inputs/gc-debuginfo-mod2.c -c -o %t2.o +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -O1 -fno-inline \ +// RUN: -fuse-ld=lld -Wl,--gc-sections,--gc-debuginfo %t.o %t1.o %t2.o -o %t3.out +// RUN: %test_debuginfo %s %t3.out + +// UNSUPPORTED: system-darwin + +// This test checks that location lists are valid after --gc-debuginfo done. +// It steps into getModule1Value() and getModule2Value()(located in different +// object files) and prints values of internal variable result. It should +// print proper values if location lists are correct. + +#include + +int mod1_f1(); +int mod2_f1(); + +__attribute__((optnone)) void func(int p) { + printf("\n %d", p); +} + +int main(void) { + func(mod1_f1(1)); + + func(mod2_f1(2)); + + return 0; +} + +// DEBUGGER: break main +// CHECK: gc-debuginfo.c +// DEBUGGER: r +// DEBUGGER: s +// CHECK: mod1_f1 (p1=1) +// DEBUGGER: s +// DEBUGGER: p r +// CHECK: = 1 +// DEBUGGER: n +// DEBUGGER: s +// CHECK: mod2_f1 (p1=2) +// DEBUGGER: s +// DEBUGGER: p r +// CHECK: = 4 +// DEBUGGER: n +// CHECK: return 0 diff --git a/debuginfo-tests/llgdb-tests/dwarflinker/inline.c b/debuginfo-tests/llgdb-tests/dwarflinker/inline.c new file mode 100644 --- /dev/null +++ b/debuginfo-tests/llgdb-tests/dwarflinker/inline.c @@ -0,0 +1,35 @@ +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O -flto=full %s -c -o %t.o +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -ffunction-sections \ +// RUN: -O -flto=full %p/Inputs/inline-foo.c -c -o %t1.o +// RUN: %clang %target_itanium_abi_host_triple -gdwarf-4 -O -flto=full \ +// RUN: -fuse-ld=lld -Wl,--gc-sections,--gc-debuginfo %t.o %t1.o -o %t3.out +// RUN: %test_debuginfo %s %t3.out + +// UNSUPPORTED: system-darwin + +// This test checks that debug info for inlined function is properly preserved +// when --gc-debuginfo is used. It is compiled with -flto=full, so that function +// foo would be inlined. It checks that debugger shows local variables and +// function name of inlined routine correctly. + +#include + +int foo(int ); + +int main(void) { + return foo(3); +} + +// DEBUGGER: b foo +// CHECK: inline-foo.c +// DEBUGGER: r +// DEBUGGER: si +// CHECK: in foo +// DEBUGGER: info locals +// CHECK: i = 0 +// CHECK: r = 11 +// DEBUGGER: info address main +// CHECK: Symbol "main" is a function +// DEBUGGER: info address foo +// CHECK: Symbol "foo" is a function diff --git a/lld/Common/DWARF.cpp b/lld/Common/DWARF.cpp --- a/lld/Common/DWARF.cpp +++ b/lld/Common/DWARF.cpp @@ -58,9 +58,8 @@ // two variables in different namespaces of the same object. Use common // name otherwise, but handle the case when it also absent in case if the // input object file lacks some debug info. - StringRef name = - dwarf::toString(die.find(dwarf::DW_AT_linkage_name), - dwarf::toString(die.find(dwarf::DW_AT_name), "")); + StringRef name = dwarf::toString( + die.find({dwarf::DW_AT_linkage_name, dwarf::DW_AT_name}), ""); if (!name.empty()) variableLoc.insert({name, {lt, file, line}}); } diff --git a/lld/ELF/CMakeLists.txt b/lld/ELF/CMakeLists.txt --- a/lld/ELF/CMakeLists.txt +++ b/lld/ELF/CMakeLists.txt @@ -30,6 +30,7 @@ LTO.cpp LinkerScript.cpp MapFile.cpp + LLDDwarfLinker.cpp MarkLive.cpp OutputSections.cpp Relocations.cpp @@ -48,6 +49,8 @@ BitWriter Core DebugInfoDWARF + DWARFLinker + AsmPrinter Demangle LTO MC @@ -55,6 +58,9 @@ Option Passes Support + AllTargetsCodeGens + AllTargetsDescs + AllTargetsInfos LINK_LIBS lldCommon diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -150,6 +150,7 @@ bool compressDebugSections; bool cref; std::vector> deadRelocInNonAlloc; + bool debugNames; bool defineCommon; bool demangle = true; bool dependentLibraries; @@ -163,6 +164,8 @@ bool fixCortexA53Errata843419; bool fixCortexA8; bool formatBinary = false; + bool gcDebugInfo; + bool gcDebugInfoNoOdr = false; bool gcSections; bool gdbIndex; bool gnuHash = false; diff --git a/lld/ELF/DWARF.h b/lld/ELF/DWARF.h --- a/lld/ELF/DWARF.h +++ b/lld/ELF/DWARF.h @@ -10,8 +10,10 @@ #define LLD_ELF_DWARF_H #include "InputFiles.h" +#include "Relocations.h" #include "llvm/ADT/STLExtras.h" #include "llvm/DebugInfo/DWARF/DWARFContext.h" +#include "llvm/DebugInfo/DWARF/DWARFSection.h" #include "llvm/Object/ELF.h" namespace lld { @@ -21,6 +23,10 @@ struct LLDDWARFSection final : public llvm::DWARFSection { InputSectionBase *sec = nullptr; + + /// Index into relocations array of the next relocation to consider. + mutable uint64_t nextEnumReloc = 0; + mutable uint64_t nextDwarfObjReloc = 0; }; template class LLDDwarfObj final : public llvm::DWARFObject { @@ -68,6 +74,10 @@ return gnuPubtypesSection; } + const llvm::DWARFSection &getLocSection() const override { + return locSection; + } + StringRef getFileName() const override { return ""; } StringRef getAbbrevSection() const override { return abbrevSection; } StringRef getStrSection() const override { return strSection; } @@ -80,11 +90,30 @@ llvm::Optional find(const llvm::DWARFSection &sec, uint64_t pos) const override; + ArrayRef getSectionNames() const override { + return sectionNames; + } + + /// This function calls specified handler for all relocations + /// pointing to live section and located from "startPos" till "endPos" + /// for the section "s". + void enumerateRelocations( + const llvm::DWARFSection &s, uint64_t startPos, uint64_t endPos, + llvm::function_ref + relHandler) const; + private: template - llvm::Optional findAux(const InputSectionBase &sec, - uint64_t pos, - ArrayRef rels) const; + llvm::Optional + findAux(const InputSectionBase &sec, uint64_t &nextDwarfObjReloc, + uint64_t pos, ArrayRef rels) const; + + template + void enumerateRelocations( + const LLDDWARFSection &sec, ArrayRef rels, uint64_t startPos, + uint64_t endPos, + llvm::function_ref + relHandler) const; LLDDWARFSection gnuPubnamesSection; LLDDWARFSection gnuPubtypesSection; @@ -95,9 +124,12 @@ LLDDWARFSection strOffsetsSection; LLDDWARFSection lineSection; LLDDWARFSection addrSection; + LLDDWARFSection locSection; StringRef abbrevSection; StringRef strSection; StringRef lineStrSection; + + std::vector sectionNames; }; } // namespace elf diff --git a/lld/ELF/DWARF.cpp b/lld/ELF/DWARF.cpp --- a/lld/ELF/DWARF.cpp +++ b/lld/ELF/DWARF.cpp @@ -17,8 +17,8 @@ #include "Symbols.h" #include "Target.h" #include "lld/Common/Memory.h" +#include "llvm/BinaryFormat/ELF.h" #include "llvm/DebugInfo/DWARF/DWARFDebugPubTable.h" -#include "llvm/Object/ELFObjectFile.h" using namespace llvm; using namespace llvm::object; @@ -29,11 +29,16 @@ // Get the ELF sections to retrieve sh_flags. See the SHF_GROUP comment below. ArrayRef objSections = CHECK(obj->getObj().sections(), obj); + StringMap sectionNumber; assert(objSections.size() == obj->getSections().size()); for (auto it : llvm::enumerate(obj->getSections())) { InputSectionBase *sec = it.value(); - if (!sec) + if (!sec) { + sectionNames.push_back({"", true}); continue; + } + sectionNames.push_back({sec->name, true}); + sectionNumber[sec->name]++; if (LLDDWARFSection *m = StringSwitch(sec->name) @@ -45,6 +50,7 @@ .Case(".debug_rnglists", &rnglistsSection) .Case(".debug_str_offsets", &strOffsetsSection) .Case(".debug_line", &lineSection) + .Case(".debug_loc", &locSection) .Default(nullptr)) { m->Data = toStringRef(sec->data()); m->sec = sec; @@ -72,6 +78,9 @@ infoSection.sec = sec; } } + for (SectionName &sec : sectionNames) + if (!sec.Name.empty() && sectionNumber.lookup(sec.Name) > 1) + sec.IsNameUnique = false; } namespace { @@ -93,22 +102,42 @@ }; } // namespace +template +const RelTy *findReloc(const RelTy *rel, ArrayRef rels, uint64_t pos) { + // When dwarf->compile_units() is processed most of relocations are + // accessed sequentally. It is very often that next relocation is close + // to previous one. Searching for the next 4 relocations covers 90% cases. + const RelTy *end = std::min(rel + 4, rels.end()); + for (; rel < end; ++rel) + if (rel->r_offset == pos) + return rel; + + rel = partition_point(rels, [=](const RelTy &a) { return a.r_offset < pos; }); + + if (rel == rels.end() || rel->r_offset != pos) + return nullptr; + + return rel; +} + // Find if there is a relocation at Pos in Sec. The code is a bit // more complicated than usual because we need to pass a section index // to llvm since it has no idea about InputSection. template template Optional -LLDDwarfObj::findAux(const InputSectionBase &sec, uint64_t pos, +LLDDwarfObj::findAux(const InputSectionBase &sec, + uint64_t &nextDwarfObjReloc, uint64_t pos, ArrayRef rels) const { - auto it = - partition_point(rels, [=](const RelTy &a) { return a.r_offset < pos; }); - if (it == rels.end() || it->r_offset != pos) + const RelTy *rel = findReloc(rels.begin() + nextDwarfObjReloc, rels, pos); + + if (!rel) return None; - const RelTy &rel = *it; + + nextDwarfObjReloc = rel - rels.begin() + 1; const ObjFile *file = sec.getFile(); - uint32_t symIndex = rel.getSymbol(config->isMips64EL); + uint32_t symIndex = rel->getSymbol(config->isMips64EL); const typename ELFT::Sym &sym = file->template getELFSyms()[symIndex]; uint32_t secIndex = file->getSectionIndex(sym); @@ -116,13 +145,13 @@ // shall still resolve it. This is important for --gdb-index: the end address // offset of an entry in .debug_ranges is relocated. If it is not resolved, // its zero value will terminate the decoding of .debug_ranges prematurely. - Symbol &s = file->getRelocTargetSym(rel); + Symbol &s = file->getRelocTargetSym(*rel); uint64_t val = 0; if (auto *dr = dyn_cast(&s)) val = dr->value; DataRefImpl d; - d.p = getAddend(rel); + d.p = getAddend(*rel); return RelocAddrEntry{secIndex, RelocationRef(d, nullptr), val, Optional(), 0, LLDRelocationResolver::resolve}; @@ -133,8 +162,78 @@ uint64_t pos) const { auto &sec = static_cast(s); if (sec.sec->areRelocsRela) - return findAux(*sec.sec, pos, sec.sec->template relas()); - return findAux(*sec.sec, pos, sec.sec->template rels()); + return findAux(*sec.sec, sec.nextDwarfObjReloc, pos, + sec.sec->template relas()); + return findAux(*sec.sec, sec.nextDwarfObjReloc, pos, + sec.sec->template rels()); +} + +// This function calls specified handler for all relocations +// pointing to live section and located from "startPos" till "endPos" +// for the section "s". +template +void LLDDwarfObj::enumerateRelocations( + const llvm::DWARFSection &s, uint64_t startPos, uint64_t endPos, + llvm::function_ref + relHandler) const { + auto &sec = static_cast(s); + if (sec.sec->areRelocsRela) + enumerateRelocations(sec, sec.sec->template relas(), startPos, endPos, + relHandler); + else + enumerateRelocations(sec, sec.sec->template rels(), startPos, endPos, + relHandler); +} + +// This function calls specified handler for all relocations pointing +// to live section and located from "startPos" till "endPos" from section +// relocations(rels). +// Assumption: "startPos"/"endPos" are always specified in increasing +// order(i.e. "enumerateRelocations" are always called with "startPos"/"endPos" +// having greater value than in previous call). "rels" contains +// relocations sorted on r_offset basis. +template +template +void LLDDwarfObj::enumerateRelocations( + const LLDDWARFSection &sec, ArrayRef rels, uint64_t startPos, + uint64_t endPos, + llvm::function_ref + relHandler) const { + // Check if there are no more relocations. + if (sec.nextEnumReloc >= rels.size()) + return; + + // Skip relocations, started from nextReloc until relocPos < startPos. + uint64_t relocPos = rels[sec.nextEnumReloc].r_offset; + while (relocPos < startPos && sec.nextEnumReloc < rels.size() - 1) + relocPos = rels[++sec.nextEnumReloc].r_offset; + + // Enumerate relocations in range [startPos, endPos). + const ObjFile *file = sec.sec->getFile(); + while (relocPos >= startPos && relocPos < endPos) { + const RelTy &rel = rels[sec.nextEnumReloc]; + const typename ELFT::Sym &sym = + file->template getELFSyms()[rel.getSymbol(config->isMips64EL)]; + uint32_t secIndex = file->getSectionIndex(sym); + + InputSectionBase *referencedSection = file->getSections()[secIndex]; + + // Check whether relocation points to live section. + if (referencedSection && referencedSection->isLive()) { + // Create architecture neutral relocation representation. + // Later we will fully calculate destination value, thus + // we do not need any expression. Set R_ABS then. + Relocation reloc = {R_ABS, rel.getType(config->isMips64EL), rel.r_offset, + getAddend(rel), &file->getRelocTargetSym(rel)}; + + relHandler(reloc, referencedSection); + } + + if (++sec.nextEnumReloc >= rels.size()) + break; + + relocPos = rels[sec.nextEnumReloc].r_offset; + } } template class elf::LLDDwarfObj; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -296,6 +296,7 @@ static void initLLVM() { InitializeAllTargets(); InitializeAllTargetMCs(); + InitializeAllTargetInfos(); InitializeAllAsmPrinters(); InitializeAllAsmParsers(); } @@ -341,6 +342,8 @@ if (config->relocatable) { if (config->shared) error("-r and -shared may not be used together"); + if (config->gcDebugInfo) + error("-r and --gc-debuginfo may not be used together"); if (config->gdbIndex) error("-r and --gdb-index may not be used together"); if (config->icf != ICFLevel::None) @@ -351,6 +354,15 @@ error("-r and --export-dynamic may not be used together"); } + if (config->gcDebugInfo && !config->gcSections) + error("--gc-debuginfo may not be used without --gc-sections"); + + if (config->gcDebugInfoNoOdr && !config->gcSections) + error("--gc-debuginfo-noodr may not be used without --gc-sections"); + + if (!config->gcDebugInfo && config->debugNames) + error("--debug-names may not be used without --gc-debuginfo"); + if (config->executeOnly) { if (config->emachine != EM_AARCH64) error("-execute-only is only supported on AArch64 targets"); @@ -928,6 +940,7 @@ !args.hasArg(OPT_relocatable)); config->optimizeBBJumps = args.hasFlag(OPT_optimize_bb_jumps, OPT_no_optimize_bb_jumps, false); + config->debugNames = args.hasFlag(OPT_debug_names, OPT_no_debug_names, false); config->demangle = args.hasFlag(OPT_demangle, OPT_no_demangle, true); config->dependencyFile = args.getLastArgValue(OPT_dependency_file); config->dependentLibraries = args.hasFlag(OPT_dependent_libraries, OPT_no_dependent_libraries, true); @@ -955,6 +968,10 @@ config->fixCortexA8 = args.hasArg(OPT_fix_cortex_a8) && !args.hasArg(OPT_relocatable); config->gcSections = args.hasFlag(OPT_gc_sections, OPT_no_gc_sections, false); + config->gcDebugInfo = + args.hasFlag(OPT_gc_debuginfo, OPT_no_gc_debuginfo, false); + config->gcDebugInfoNoOdr = + args.hasFlag(OPT_gc_debuginfo_noodr, OPT_no_gc_debuginfo_noodr, false); config->gnuUnique = args.hasFlag(OPT_gnu_unique, OPT_no_gnu_unique, true); config->gdbIndex = args.hasFlag(OPT_gdb_index, OPT_no_gdb_index, false); config->icf = getICF(args); @@ -2141,11 +2158,11 @@ if (config->strip == StripPolicy::None) return false; - if (isDebugSection(*s)) + if (isDebugSection(s->name)) return true; if (auto *isec = dyn_cast(s)) if (InputSectionBase *rel = isec->getRelocatedSection()) - if (isDebugSection(*rel)) + if (isDebugSection(rel->name)) return true; return false; diff --git a/lld/ELF/InputSection.h b/lld/ELF/InputSection.h --- a/lld/ELF/InputSection.h +++ b/lld/ELF/InputSection.h @@ -390,8 +390,8 @@ template void copyShtGroup(uint8_t *buf); }; -inline bool isDebugSection(const InputSectionBase &sec) { - return sec.name.startswith(".debug") || sec.name.startswith(".zdebug"); +inline bool isDebugSection(StringRef name) { + return name.startswith(".debug") || name.startswith(".zdebug"); } // The list of all input sections. diff --git a/lld/ELF/InputSection.cpp b/lld/ELF/InputSection.cpp --- a/lld/ELF/InputSection.cpp +++ b/lld/ELF/InputSection.cpp @@ -449,7 +449,7 @@ // See the comment in maybeReportUndefined for PPC32 .got2 and PPC64 .toc auto *d = dyn_cast(&sym); if (!d) { - if (!isDebugSection(*sec) && sec->name != ".eh_frame" && + if (!isDebugSection(sec->name) && sec->name != ".eh_frame" && sec->name != ".gcc_except_table" && sec->name != ".got2" && sec->name != ".toc") { uint32_t secIdx = cast(sym).discardedSecIdx; @@ -861,7 +861,7 @@ template void InputSection::relocateNonAlloc(uint8_t *buf, ArrayRef rels) { const unsigned bits = sizeof(typename ELFT::uint) * 8; - const bool isDebug = isDebugSection(*this); + const bool isDebug = isDebugSection(name); const bool isDebugLocOrRanges = isDebug && (name == ".debug_loc" || name == ".debug_ranges"); const bool isDebugLine = isDebug && name == ".debug_line"; diff --git a/lld/ELF/LLDDwarfLinker.h b/lld/ELF/LLDDwarfLinker.h new file mode 100644 --- /dev/null +++ b/lld/ELF/LLDDwarfLinker.h @@ -0,0 +1,28 @@ +//===- LLDDwarfLinker.h -----------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLD_ELF_LLDDWARFLINKER_H +#define LLD_ELF_LLDDWARFLINKER_H + +#include "Writer.h" +#include "llvm/ADT/SmallString.h" + +namespace lld { +namespace elf { + +// This function is used to remove an unused parts of debug data +// which belongs to garbage collected sections. It writes linked +// debug info into specified outDebugInfoBytes container and links +// output sections to their appropriate portions of debug info from +// outDebugInfoBytes. +template void linkDebugInfo(DebugDataBits &outDebugInfoBytes); + +} // namespace elf +} // namespace lld + +#endif // LLD_ELF_LLDDWARFLINKER_H diff --git a/lld/ELF/LLDDwarfLinker.cpp b/lld/ELF/LLDDwarfLinker.cpp new file mode 100644 --- /dev/null +++ b/lld/ELF/LLDDwarfLinker.cpp @@ -0,0 +1,293 @@ +//===- LLDDwarfLinker.cpp -------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements --gc-debuginfo functionality: +// +// When the linker does garbage collection, a lot of abandoned debug info +// is left behind. To remove such abandoned debug info LLDDwarfLinker analyzes +// relocations and removes debug info related to deleted sections. +// +// The process of linking debug info is based on liveness information created by +// markLive(). AddressesMap::getValidAddressRanges() creates a list +// of address ranges which points into live sections. DWARFLinker searches +// for 'root DIEs' related to live address ranges and calls cloneDIE() +// recursively. Cloned dies are passed to DWARFStreamer through DwarfEmitter +// interface. DWARFStreamer writes data into plain memory container. +// Corresponding sections of that container are linked with output sections. +// +//===----------------------------------------------------------------------===// + +#include "LLDDwarfLinker.h" +#include "DWARF.h" +#include "InputFiles.h" +#include "InputSection.h" +#include "OutputSections.h" +#include "Symbols.h" +#include "Target.h" +#include "lld/Common/DWARF.h" +#include "lld/Common/ErrorHandler.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/Triple.h" +#include "llvm/DWARFLinker/DWARFLinker.h" +#include "llvm/DWARFLinker/DWARFStreamer.h" +#include "llvm/Object/ELF.h" +#include "llvm/Object/ELFObjectFile.h" +#include "llvm/Support/Parallel.h" +#include "llvm/Support/SmallVectorMemoryBuffer.h" +#include "llvm/Target/TargetLoweringObjectFile.h" + +using namespace llvm; +using namespace llvm::ELF; +using namespace llvm::object; + +namespace lld { +namespace elf { + +template class ObjFileAddressMap : public AddressesMap { +public: + ObjFileAddressMap(DWARFCache &dwarf, ObjFile *objFile) + : dwarf(dwarf), objFile(objFile), + dwarfObj(static_cast &>( + dwarf.getContext()->getDWARFObj())) { + + // Create map InputSectionBase -> SectionIndex. + DenseMap secToIndex; + uint64_t curIdx = 0; + for (InputSectionBase *sec : objFile->getSections()) { + if (sec) + secToIndex[sec] = curIdx; + ++curIdx; + } + + // Discover live address ranges. + for (const Symbol *symbol : objFile->symbols) { + if (symbol->isFunc() || symbol->isObject()) { + if (symbol->kind() == Symbol::DefinedKind) { + InputSectionBase *sec = + cast(cast(*symbol).section); + + if (!sec || !sec->isLive() || !secToIndex.count(sec)) + continue; + + hasValidRelocations = true; + + SymbolAddresses addr = getSymbolAddresses(symbol, sec); + addressRanges[{addr.objectAddress, secToIndex[sec]}] = { + addr.objectAddress + symbol->getSize(), + (int64_t)addr.inputSectionAddress}; + } + } + } + } + + bool hasValidRelocs(bool resetRelocsPtr = true) override { + // TODO: Support the case multiplue .debug_info sections. + // TODO: HasValidRelocs should receive section + // TODO: which should be examined. + if (resetRelocsPtr) + dwarfObj.forEachInfoSections([&](const llvm::DWARFSection &sec) { + auto &debugInfoSec = static_cast(sec); + debugInfoSec.nextEnumReloc = 0; + }); + + return hasValidRelocations; + } + + bool hasValidRelocationAt(uint64_t startOffset, uint64_t endOffset, + CompileUnit::DIEInfo &info) override { + + bool ret = false; + + // TODO: Support the case multiplue .debug_info sections. + // TODO: HasValidRelocationAt should receive section + // TODO: which should be examined. + dwarfObj.forEachInfoSections([&](const llvm::DWARFSection &sec) { + dwarfObj.enumerateRelocations( + sec, startOffset, endOffset, + [&](Relocation rel, const InputSectionBase *sec) { + if (!ret) { + if (!isDebugSection(sec->name)) { + info.AddrAdjust = + getSymbolAddresses(rel.sym, sec).inputSectionAddress; + info.InDebugMap = true; + ret = true; + } + } + }); + }); + + return ret; + } + + bool applyValidRelocs(MutableArrayRef data, uint64_t baseOffset, + bool) override { + bool ret = false; + + // TODO: Support the case multiplue .debug_info sections. + // TODO: ApplyValidRelocs should receive section + // TODO: which should be examined. + dwarfObj.forEachInfoSections([&](const llvm::DWARFSection &sec) { + dwarfObj.enumerateRelocations( + sec, baseOffset, baseOffset + data.size(), + [&](Relocation rel, const InputSectionBase *sec) { + SymbolAddresses Addr = getSymbolAddresses(rel.sym, sec); + assert(rel.offset - baseOffset < data.size()); + + // Apply relocation. + getTarget()->relocate( + (uint8_t *)(data.data() + rel.offset - baseOffset), rel, + Addr.inputSectionAddress + Addr.objectAddress + rel.addend); + ret = true; + }); + }); + + return ret; + } + + RangesTy &getValidAddressRanges() override { return addressRanges; }; + + void clear() override { addressRanges.clear(); } + +private: + struct SymbolAddresses { + uint64_t objectAddress = 0; + uint64_t inputSectionAddress = 0; + }; + + // Returns object address(symbol value) and address of input section. + SymbolAddresses + getSymbolAddresses(const Symbol *sym, + const InputSectionBase *referencedSection) { + SymbolAddresses result; + + if (sym && sym->kind() == Symbol::DefinedKind) { + auto &definedSym = cast(*sym); + + result.objectAddress = definedSym.value; + + if (referencedSection && !isDebugSection(referencedSection->name)) { + result.inputSectionAddress = + referencedSection->getVA(definedSym.value) - definedSym.value; + } + } + + return result; + } + + RangesTy addressRanges; + DWARFCache &dwarf; + ObjFile *objFile; + const LLDDwarfObj &dwarfObj; + bool hasValidRelocations = false; +}; + +// This function is used to remove an unused parts of debug data +// which belongs to garbage collected sections. +template void linkDebugInfo(DebugDataBits &outDebugInfoBytes) { + // Calculate estimated size of resulting debug info. + // It would reserve half of original debug info size. + // That is average ratio of debug info size decreasing. + uint64_t estimatedSize = 0; + for (const InputSectionBase *sec : inputSections) + if (isDebugSection(sec->name) && sec->isLive()) + estimatedSize += sec->getSize() * 0.5f; + outDebugInfoBytes.reserve(estimatedSize); + + auto ReportWarn = [&](const Twine &message, StringRef context, + const DWARFDie *) { + if (context.empty()) + warn(message); + else + warn(context + ": " + message); + }; + + // Create output streamer. + raw_svector_ostream stream(outDebugInfoBytes); + DwarfStreamer outStreamer(OutputFileType::Object, stream, + [](StringRef input) { return input; }, false, + ReportWarn, ReportWarn); + if (!outStreamer.init(Triple(sys::getProcessTriple()))) + return; + + // Create DWARF linker. + DWARFLinker debugInfoLinker(&outStreamer, DwarfLinkerClient::LLD); + + debugInfoLinker.setEstimatedObjfilesAmount(objectFiles.size()); + debugInfoLinker.setAccelTableKind(config->debugNames ? AccelTableKind::Dwarf + : AccelTableKind::None); + debugInfoLinker.setErrorHandler(ReportWarn); + debugInfoLinker.setWarningHandler(ReportWarn); + debugInfoLinker.setNumThreads(llvm::parallel::strategy.ThreadsRequested); + debugInfoLinker.setNoODR(config->gcDebugInfoNoOdr); + + std::vector> objectsForLinking(objectFiles.size()); + std::vector> addresssMapForLinking( + objectFiles.size()); + std::vector emptyWarnings; + + // Add object files to the DWARFLinker. + parallelForEachN(0, objectFiles.size(), [&](size_t i) { + if (ObjFile *obj = cast>(objectFiles[i])) { + addresssMapForLinking[i] = + std::make_unique>(*obj->getDwarf(), obj); + + objectsForLinking[i] = std::make_unique( + objectFiles[i]->getName(), obj->getDwarf()->getContext(), + addresssMapForLinking[i].get(), emptyWarnings); + } + }); + + for (size_t i = 0; i < objectFiles.size(); i++) + debugInfoLinker.addObjectFile(*objectsForLinking[i]); + + // Link debug info. + debugInfoLinker.link(); + outStreamer.finish(); + + if (outDebugInfoBytes.empty()) + return; + + // Create sections map: Section Name -> Section Bits. + // outDebugInfoBytes contains elf file with all + // generated debug sections. We create a map here to assign + // section bits to output section later. + DenseMap sectionsMap; + if (Expected> MemFile = + ELFObjectFile::create({outDebugInfoBytes, ""})) { + for (const SectionRef &Sec : MemFile->sections()) { + if (Expected name = Sec.getName()) { + if (!isDebugSection(*name)) + continue; + + if (Expected contents = Sec.getContents()) + sectionsMap[*name] = *contents; + else + sectionsMap[*name] = ""; + } else { + warn("Empty section name inside generated debug info"); + return; + } + } + } else { + warn("Generated debug info is broken"); + return; + } + + // Set linked debug info to corresponding output section. + for (OutputSection *sec : outputSections) + if (isDebugSection(sec->name)) + sec->setDebugData(sectionsMap.lookup(sec->name)); +} + +template void linkDebugInfo(DebugDataBits &); +template void linkDebugInfo(DebugDataBits &); +template void linkDebugInfo(DebugDataBits &); +template void linkDebugInfo(DebugDataBits &); + +} // namespace elf +} // namespace lld diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -124,6 +124,10 @@ "Output cross reference table", "Do not output cross reference table">; +defm debug_names: B<"debug-names", + "Enable .debug_names table generation", + "Disable .debug_names table generation (default)">; + defm define_common: B<"define-common", "Assign space to common symbols", "Do not assign space to common symbols">; @@ -217,6 +221,14 @@ "Enable garbage collection of unused sections", "Disable garbage collection of unused sections (default)">; +defm gc_debuginfo: B<"gc-debuginfo", + "Enable garbage collection of unused debug information", + "Disable garbage collection of unused debug information (default)">; + +defm gc_debuginfo_noodr: B<"gc-debuginfo-no-odr", + "Disable ODR types de-duplication", + "Enable ODR types de-duplication (default)">; + defm gdb_index: BB<"gdb-index", "Generate .gdb_index section", "Do not generate .gdb_index section (default)">; diff --git a/lld/ELF/OutputSections.h b/lld/ELF/OutputSections.h --- a/lld/ELF/OutputSections.h +++ b/lld/ELF/OutputSections.h @@ -108,12 +108,23 @@ void sortInitFini(); void sortCtorsDtors(); + void setDebugData(StringRef data) { + debugData = data; + size = data.size(); + } + + bool hasDebugData(void) const { return debugData.hasValue(); } + private: // Used for implementation of --compress-debug-sections option. std::vector zDebugHeader; llvm::SmallVector compressedData; std::array getFiller(); + + // Used for assign debug data generated by DWARFLinker + // for concrete output section. + llvm::Optional debugData; }; int getPriority(StringRef s); diff --git a/lld/ELF/OutputSections.cpp b/lld/ELF/OutputSections.cpp --- a/lld/ELF/OutputSections.cpp +++ b/lld/ELF/OutputSections.cpp @@ -335,6 +335,12 @@ return; } + // If there are data generated by dwarflinker just write it down. + if (hasDebugData()) { + memcpy(buf, debugData->data(), debugData->size()); + return; + } + // Write leading padding. std::vector sections = getInputSections(this); std::array filler = getFiller(); diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp --- a/lld/ELF/Relocations.cpp +++ b/lld/ELF/Relocations.cpp @@ -1549,7 +1549,6 @@ for (auto i = rels.begin(), end = rels.end(); i != end;) scanReloc(sec, getOffset, i, rels.begin(), end); - // Sort relocations by offset for more efficient searching for // R_RISCV_PCREL_HI20 and R_PPC64_ADDR64. if (config->emachine == EM_RISCV || diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h --- a/lld/ELF/SyntheticSections.h +++ b/lld/ELF/SyntheticSections.h @@ -789,6 +789,17 @@ size_t size; }; +/// DebugSection is used to keep data generated by DWARFLinker. +class DebugSection final : public SyntheticSection { +public: + DebugSection(StringRef name); + template static DebugSection *create(StringRef name); + + void writeTo(uint8_t *) override {} + size_t getSize() const override { return 0; } + bool isNeeded() const override { return true; } +}; + // --eh-frame-hdr option tells linker to construct a header for all the // .eh_frame sections. This header is placed to a section named .eh_frame_hdr // and also to a PT_GNU_EH_FRAME segment. diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp --- a/lld/ELF/SyntheticSections.cpp +++ b/lld/ELF/SyntheticSections.cpp @@ -2961,6 +2961,13 @@ bool GdbIndexSection::isNeeded() const { return !chunks.empty(); } +DebugSection::DebugSection(StringRef name) + : SyntheticSection(0, SHT_PROGBITS, 1, name) {} + +template DebugSection *DebugSection::create(StringRef name) { + return make(name); +} + EhFrameHeader::EhFrameHeader() : SyntheticSection(SHF_ALLOC, SHT_PROGBITS, 4, ".eh_frame_hdr") {} @@ -3782,6 +3789,11 @@ std::vector elf::partitions; Partition *elf::mainPart; +template DebugSection *DebugSection::create(StringRef); +template DebugSection *DebugSection::create(StringRef); +template DebugSection *DebugSection::create(StringRef); +template DebugSection *DebugSection::create(StringRef); + template GdbIndexSection *GdbIndexSection::create(); template GdbIndexSection *GdbIndexSection::create(); template GdbIndexSection *GdbIndexSection::create(); diff --git a/lld/ELF/Writer.h b/lld/ELF/Writer.h --- a/lld/ELF/Writer.h +++ b/lld/ELF/Writer.h @@ -25,6 +25,8 @@ void combineEhSections(); template void writeResult(); +using DebugDataBits = SmallString<4096>; + // This describes a program header entry. // Each contains type, access flags and range of output sections that will be // placed in it. diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp --- a/lld/ELF/Writer.cpp +++ b/lld/ELF/Writer.cpp @@ -11,6 +11,7 @@ #include "ARMErrataFix.h" #include "CallGraphSort.h" #include "Config.h" +#include "LLDDwarfLinker.h" #include "LinkerScript.h" #include "MapFile.h" #include "OutputSections.h" @@ -81,6 +82,7 @@ void writeBuildId(); std::unique_ptr &buffer; + DebugDataBits debugInfoBytes; void addRelIpltSymbols(); void addStartEndSymbols(); @@ -530,6 +532,9 @@ if (config->gdbIndex) add(GdbIndexSection::create()); + if (config->debugNames) + add(DebugSection::create(".debug_names")); + // We always need to add rel[a].plt to output if it has entries. // Even for static linking it can contain R_[*]_IRELATIVE relocations. in.relaPlt = make>( @@ -595,6 +600,10 @@ if (errorCount()) return; + // If -gc-debuginfo specified remove unused debug data. + if (config->gcDebugInfo) + linkDebugInfo(debugInfoBytes); + // If -compressed-debug-sections is specified, we need to compress // .debug_* sections. Do it right now because it changes the size of // output sections. @@ -1105,7 +1114,8 @@ // Note that relocations for non-alloc sections are directly // processed by InputSection::relocateNonAlloc. for (InputSectionBase *isec : inputSections) - if (isec->isLive() && isa(isec) && (isec->flags & SHF_ALLOC)) + if (isec->isLive() && isa(isec) && + ((isec->flags & SHF_ALLOC))) fn(*isec); for (Partition &part : partitions) { for (EhInputSection *es : part.ehFrame->sections) diff --git a/lld/docs/ld.lld.1 b/lld/docs/ld.lld.1 --- a/lld/docs/ld.lld.1 +++ b/lld/docs/ld.lld.1 @@ -140,6 +140,8 @@ .Ql --defsym=foo=bar or .Ql --defsym=foo=bar+0x100 . +.It Fl -debug-names +Enable .debug_names table generation. .It Fl -demangle Demangle symbol names. .It Fl -disable-new-dtags @@ -215,6 +217,8 @@ .Cm default is a synonym for .Cm elf . +.It Fl -gc-debuginfo +Enable removing obsolete debug info. .It Fl -gc-sections Enable garbage collection of unused sections. .It Fl -gdb-index @@ -290,6 +294,8 @@ for shared libraries. .It Fl -no-color-diagnostics Do not use colors in diagnostics. +.It Fl -no-debug-names +Disable .debug_names table generation. .It Fl -no-define-common Do not assign space to common symbols. .It Fl -no-demangle @@ -298,6 +304,8 @@ Inhibit output of an .Li .interp section. +.It Fl -no-gc-debuginfo +Disable removing obsolete debug info. .It Fl -no-gc-sections Disable garbage collection of unused sections. .It Fl -no-gnu-unique diff --git a/lld/test/ELF/gc-debuginfo-cl-errors.s b/lld/test/ELF/gc-debuginfo-cl-errors.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/gc-debuginfo-cl-errors.s @@ -0,0 +1,16 @@ +# REQUIRES: x86 + +# RUN: echo '.global _start; .global main; _start: jmp main; main: ' \ +# RUN: | llvm-mc --filetype=obj --triple=x86_64-unknown-linux - -o %t.o + +# RUN: not ld.lld -r --gc-debuginfo %t.o -o /dev/null 2>&1 \ +# RUN: | FileCheck --check-prefix=RELOC %s + +# RUN: not ld.lld --gc-debuginfo %t.o -o /dev/null 2>&1 \ +# RUN: | FileCheck --check-prefix=GCSEC %s + +## This test checks that proper error message is displayed +## if --gc-debuginfo command line flag is used incorrectly. + +# RELOC: error: -r and --gc-debuginfo may not be used together +# GCSEC: error: --gc-debuginfo may not be used without --gc-sections diff --git a/lld/test/ELF/gc-debuginfo.test b/lld/test/ELF/gc-debuginfo.test new file mode 100644 --- /dev/null +++ b/lld/test/ELF/gc-debuginfo.test @@ -0,0 +1,56 @@ +# REQUIRES: x86 + +# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o +# RUN: ld.lld %t.o -o %t.default --gc-sections --gc-debuginfo +# RUN: llvm-readelf --sections %t.default | FileCheck %s + +## This is a test for --gc-debuginfo feature. +## It contains .debug_info section which references +## .text.foo section. .text.foo would be deleted by --gc-sections. +## --gc-debuginfo should generate zero sized .debug_info section +## in such case. + +# CHECK: Section Headers: +# CHECK: [Nr] Name Type Address Off Size +# CHECK: [ 2] .debug_info PROGBITS {{[0-9a-fA-F]*}} {{[0-9a-fA-F]*}} 000000 + +.section .text.foo,"axG",@progbits,foo +.globl foo +.Lfunc_begin0: +foo: + ret +.Lfunc_end0: + +.section .debug_abbrev,"",@progbits + .byte 1 # Abbreviation Code + .byte 17 # DW_TAG_compile_unit + .byte 1 # DW_CHILDREN_yes + .byte 17 # DW_AT_low_pc + .byte 1 # DW_FORM_addr + .byte 18 # DW_AT_high_pc + .byte 6 # DW_FORM_data4 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 2 # Abbreviation Code + .byte 46 # DW_TAG_subprogram + .byte 0 # DW_CHILDREN_no + .byte 3 # DW_AT_name + .byte 8 # DW_FORM_string + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 0 + +.section .debug_info,"",@progbits +.Lcu_begin0: + .long .Lcu_end0 - .Lcu_begin0 - 4 + .short 4 # DWARF version number + .long 0 # Offset Into Abbrev. Section + .byte 4 # Address Size +.Ldie0: + .byte 1 # Abbrev [1] DW_TAG_compile_unit + .quad .Lfunc_begin0 # DW_AT_low_pc + .long .Lfunc_end0 - .Lfunc_begin0 # DW_AT_high_pc + .byte 2 # Abbrev [2] DW_TAG_subprogram + .asciz "foo" # DW_AT_name + .byte 0 +.Lcu_end0: diff --git a/llvm/include/llvm/DWARFLinker/DWARFLinker.h b/llvm/include/llvm/DWARFLinker/DWARFLinker.h --- a/llvm/include/llvm/DWARFLinker/DWARFLinker.h +++ b/llvm/include/llvm/DWARFLinker/DWARFLinker.h @@ -26,26 +26,22 @@ Apple, ///< .apple_names, .apple_namespaces, .apple_types, .apple_objc. Dwarf, ///< DWARF v5 .debug_names. Default, ///< Dwarf for DWARF5 or later, Apple otherwise. + None, ///< do not generate accelerator tables. }; /// Partial address range. Besides an offset, only the /// HighPC is stored. The structure is stored in a map where the LowPC is the /// key. -struct ObjFileAddressRange { +struct AddressHighPC { /// Function HighPC. - uint64_t HighPC; + uint64_t HighPC = 0; /// Offset to apply to the linked address. /// should be 0 for not-linked object file. - int64_t Offset; - - ObjFileAddressRange(uint64_t EndPC, int64_t Offset) - : HighPC(EndPC), Offset(Offset) {} - - ObjFileAddressRange() : HighPC(0), Offset(0) {} + int64_t Offset = 0; }; -/// Map LowPC to ObjFileAddressRange. -using RangesTy = std::map; +/// Map LowPC to AddressHighPC. +using RangesTy = std::map; /// AddressesMap represents information about valid addresses used /// by debug information. Valid addresses are those which points to @@ -55,11 +51,6 @@ public: virtual ~AddressesMap(); - /// Returns true if represented addresses are from linked file. - /// Returns false if represented addresses are from not-linked - /// object file. - virtual bool areRelocationsResolved() const = 0; - /// Checks that there are valid relocations against a .debug_info /// section. Reset current relocation pointer if neccessary. virtual bool hasValidRelocs(bool ResetRelocsPtr = true) = 0; diff --git a/llvm/include/llvm/DWARFLinker/DWARFLinkerCompileUnit.h b/llvm/include/llvm/DWARFLinker/DWARFLinkerCompileUnit.h --- a/llvm/include/llvm/DWARFLinker/DWARFLinkerCompileUnit.h +++ b/llvm/include/llvm/DWARFLinker/DWARFLinkerCompileUnit.h @@ -18,12 +18,24 @@ class DeclContext; +template <> struct DenseMapInfo { + static object::SectionedAddress getEmptyKey(); + + static object::SectionedAddress getTombstoneKey(); + + static unsigned getHashValue(const object::SectionedAddress &val); + + static bool isEqual(const object::SectionedAddress &lhs, + const object::SectionedAddress &rhs); +}; + template using HalfOpenIntervalMap = IntervalMap::LeafSize, IntervalMapHalfOpenInfo>; -using FunctionIntervals = HalfOpenIntervalMap; +using FunctionIntervals = + HalfOpenIntervalMap; // FIXME: Delete this structure. struct PatchLocation { @@ -80,6 +92,7 @@ StringRef ClangModuleName) : OrigUnit(OrigUnit), ID(ID), Ranges(RangeAlloc), ClangModuleName(ClangModuleName) { + Info.resize(OrigUnit.getNumDIEs()); auto CUDie = OrigUnit.getUnitDIE(false); @@ -126,7 +139,9 @@ uint64_t getLowPc() const { return LowPc; } uint64_t getHighPc() const { return HighPc; } - bool hasLabelAt(uint64_t Addr) const { return Labels.count(Addr); } + bool hasLabelAt(object::SectionedAddress Addr) const { + return Labels.count(Addr); + } Optional getUnitRangesAttribute() const { return UnitRangeAttribute; @@ -168,11 +183,12 @@ /// Add the low_pc of a label that is relocated by applying /// offset \p PCOffset. - void addLabelLowPc(uint64_t LabelLowPc, int64_t PcOffset); + void addLabelLowPc(object::SectionedAddress LabelLowPc, int64_t PcOffset); /// Add a function range [\p LowPC, \p HighPC) that is relocated by applying /// offset \p PCOffset. - void addFunctionRange(uint64_t LowPC, uint64_t HighPC, int64_t PCOffset); + void addFunctionRange(object::SectionedAddress LowPC, + object::SectionedAddress HighPC, int64_t PCOffset); /// Keep track of a DW_AT_range attribute that we will need to patch up later. void noteRangeAttribute(const DIE &Die, PatchLocation Attr); @@ -281,7 +297,7 @@ FunctionIntervals Ranges; /// The DW_AT_low_pc of each DW_TAG_label. - SmallDenseMap Labels; + SmallDenseMap Labels; /// DW_AT_ranges attributes to patch after we have gathered /// all the unit's function addresses. diff --git a/llvm/include/llvm/DebugInfo/DWARF/DWARFAbbreviationDeclaration.h b/llvm/include/llvm/DebugInfo/DWARF/DWARFAbbreviationDeclaration.h --- a/llvm/include/llvm/DebugInfo/DWARF/DWARFAbbreviationDeclaration.h +++ b/llvm/include/llvm/DebugInfo/DWARF/DWARFAbbreviationDeclaration.h @@ -9,6 +9,7 @@ #ifndef LLVM_DEBUGINFO_DWARFABBREVIATIONDECLARATION_H #define LLVM_DEBUGINFO_DWARFABBREVIATIONDECLARATION_H +#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/iterator_range.h" diff --git a/llvm/include/llvm/DebugInfo/DWARF/DWARFUnit.h b/llvm/include/llvm/DebugInfo/DWARF/DWARFUnit.h --- a/llvm/include/llvm/DebugInfo/DWARF/DWARFUnit.h +++ b/llvm/include/llvm/DebugInfo/DWARF/DWARFUnit.h @@ -241,6 +241,9 @@ /// std::map::upper_bound for address range lookup. std::map> AddrDieMap; + Optional StmtListOffset; + const char *CompilationDir = nullptr; + using die_iterator_range = iterator_range::iterator>; @@ -397,7 +400,10 @@ return getUnitDIE(ExtractUnitDIEOnly); } + Optional getStmtListOffset(); + const char *getCompilationDir(); + Optional getDWOId() { extractDIEsIfNeeded(/*CUDieOnly*/ true); return getHeader().getDWOId(); diff --git a/llvm/include/llvm/MC/MCAsmInfo.h b/llvm/include/llvm/MC/MCAsmInfo.h --- a/llvm/include/llvm/MC/MCAsmInfo.h +++ b/llvm/include/llvm/MC/MCAsmInfo.h @@ -659,6 +659,11 @@ return DwarfUsesRelocationsAcrossSections; } + void + setDwarfUseRelocationsAcrossSections(bool UsesRelocationsAcrossSections) { + DwarfUsesRelocationsAcrossSections = UsesRelocationsAcrossSections; + } + bool doDwarfFDESymbolsUseAbsDiff() const { return DwarfFDESymbolsUseAbsDiff; } bool useDwarfRegNumForCFI() const { return DwarfRegNumForCFI; } bool useParensForSymbolVariant() const { return UseParensForSymbolVariant; } diff --git a/llvm/include/llvm/Object/ObjectFile.h b/llvm/include/llvm/Object/ObjectFile.h --- a/llvm/include/llvm/Object/ObjectFile.h +++ b/llvm/include/llvm/Object/ObjectFile.h @@ -151,12 +151,24 @@ std::tie(RHS.SectionIndex, RHS.Address); } +inline bool operator<=(const SectionedAddress &LHS, + const SectionedAddress &RHS) { + return std::tie(LHS.SectionIndex, LHS.Address) <= + std::tie(RHS.SectionIndex, RHS.Address); +} + inline bool operator==(const SectionedAddress &LHS, const SectionedAddress &RHS) { return std::tie(LHS.SectionIndex, LHS.Address) == std::tie(RHS.SectionIndex, RHS.Address); } +inline bool operator!=(const SectionedAddress &LHS, + const SectionedAddress &RHS) { + return std::tie(LHS.SectionIndex, LHS.Address) != + std::tie(RHS.SectionIndex, RHS.Address); +} + raw_ostream &operator<<(raw_ostream &OS, const SectionedAddress &Addr); /// This is a value type class that represents a single symbol in the list of diff --git a/llvm/lib/DWARFLinker/DWARFLinker.cpp b/llvm/lib/DWARFLinker/DWARFLinker.cpp --- a/llvm/lib/DWARFLinker/DWARFLinker.cpp +++ b/llvm/lib/DWARFLinker/DWARFLinker.cpp @@ -436,7 +436,7 @@ std::tie(LowPcOffset, LowPcEndOffset) = getAttributeOffsets(Abbrev, *LowPcIdx, Offset, OrigUnit); - auto LowPc = dwarf::toAddress(DIE.find(dwarf::DW_AT_low_pc)); + auto LowPc = dwarf::toSectionedAddress(DIE.find(dwarf::DW_AT_low_pc)); assert(LowPc.hasValue() && "low_pc attribute is not an address."); if (!LowPc || !RelocMgr.hasValidRelocationAt(LowPcOffset, LowPcEndOffset, MyInfo)) @@ -458,7 +458,7 @@ // generation bugs aside, this is really wrong in the case of labels, where // a label marking the end of a function will have a PC == CU's high_pc. if (dwarf::toAddress(OrigUnit.getUnitDIE().find(dwarf::DW_AT_high_pc)) - .getValueOr(UINT64_MAX) <= LowPc) + .getValueOr(UINT64_MAX) <= LowPc->Address) return Flags; Unit.addLabelLowPc(*LowPc, MyInfo.AddrAdjust); return Flags | TF_Keep; @@ -466,7 +466,7 @@ Flags |= TF_Keep; - Optional HighPc = DIE.getHighPC(*LowPc); + Optional HighPc = DIE.getHighPC(LowPc->Address); if (!HighPc) { reportWarning("Function without high_pc. Range will be discarded.\n", File, &DIE); @@ -474,8 +474,9 @@ } // Replace the debug map range with a more accurate one. - Ranges[*LowPc] = ObjFileAddressRange(*HighPc, MyInfo.AddrAdjust); - Unit.addFunctionRange(*LowPc, *HighPc, MyInfo.AddrAdjust); + Ranges[*LowPc] = {*HighPc, MyInfo.AddrAdjust}; + Unit.addFunctionRange(*LowPc, {*HighPc, LowPc->SectionIndex}, + MyInfo.AddrAdjust); return Flags; } @@ -631,8 +632,9 @@ if (AttrSpec.Form != dwarf::DW_FORM_ref_addr && (UseOdr || IsModuleRef) && Info.Ctxt && Info.Ctxt != ReferencedCU->getInfo(Info.ParentIdx).Ctxt && - Info.Ctxt->getCanonicalDIEOffset() && isODRAttribute(AttrSpec.Attr)) + Info.Ctxt->getCanonicalDIEOffset() && isODRAttribute(AttrSpec.Attr)) { continue; + } // Keep a module forward declaration if there is no definition. if (!(isODRAttribute(AttrSpec.Attr) && Info.Ctxt && @@ -822,7 +824,6 @@ // Switch everything to out of line strings. const char *String = *Val.getAsCString(); auto StringEntry = StringPool.getEntry(String); - // Update attributes info. if (AttrSpec.Attr == dwarf::DW_AT_name) Info.Name = StringEntry; @@ -832,7 +833,6 @@ Die.addValue(DIEAlloc, dwarf::Attribute(AttrSpec.Attr), dwarf::DW_FORM_strp, DIEInteger(StringEntry.getOffset())); - return 4; } @@ -1054,17 +1054,17 @@ : Addr) + Info.PCOffset; else if (Die.getTag() == dwarf::DW_TAG_compile_unit) { - Addr = Unit.getLowPc(); - if (Addr == std::numeric_limits::max()) + const auto &pc = Unit.getLowPc(); + + if (pc != std::numeric_limits::max()) + Addr = pc; + else return 0; } Info.HasLowPc = true; } else if (AttrSpec.Attr == dwarf::DW_AT_high_pc) { if (Die.getTag() == dwarf::DW_TAG_compile_unit) { - if (uint64_t HighPc = Unit.getHighPc()) - Addr = HighPc; - else - return 0; + Addr = Unit.getHighPc(); } else // If we have a high_pc recorded for the input DIE, use // it. Otherwise (when no relocations where applied) just use the @@ -1114,10 +1114,11 @@ if (AttrSpec.Attr == dwarf::DW_AT_high_pc && Die.getTag() == dwarf::DW_TAG_compile_unit) { - if (Unit.getLowPc() == -1ULL) + if (Unit.getLowPc() != std::numeric_limits::max()) + // Dwarf >= 4 high_pc is an size, not an address. + Value = Unit.getHighPc() - Unit.getLowPc(); + else return 0; - // Dwarf >= 4 high_pc is an size, not an address. - Value = Unit.getHighPc() - Unit.getLowPc(); } else if (AttrSpec.Form == dwarf::DW_FORM_sec_offset) Value = *Val.getAsSectionOffset(); else if (AttrSpec.Form == dwarf::DW_FORM_sdata) @@ -1285,7 +1286,6 @@ // Should the DIE appear in the output? if (!Unit.getInfo(Idx).Keep) return nullptr; - uint64_t Offset = InputDIE.getOffset(); assert(!(Die && Info.Clone) && "Can't supply a DIE and a cloned DIE"); if (!Die) { @@ -1326,8 +1326,7 @@ DWARFDataExtractor(DIECopy, Data.isLittleEndian(), Data.getAddressSize()); // Modify the copy with relocated addresses. - if (ObjFile.Addresses->areRelocationsResolved() && - ObjFile.Addresses->applyValidRelocs(DIECopy, Offset, + if (ObjFile.Addresses->applyValidRelocs(DIECopy, Offset, Data.isLittleEndian())) { // If we applied relocations, we store the value of high_pc that was // potentially stored in the input DIE. If high_pc is an address @@ -1387,8 +1386,8 @@ DWARFFormValue Val(AttrSpec.Form); uint64_t AttrSize = Offset; Val.extractValue(Data, &Offset, U.getFormParams(), &U); - AttrSize = Offset - AttrSize; + AttrSize = Offset - AttrSize; OutOffset += cloneAttribute(*Die, InputDIE, File, Unit, StringPool, Val, AttrSpec, AttrSize, AttrInfo, IsLittleEndian); } @@ -1493,12 +1492,12 @@ auto InvalidRange = FunctionRanges.end(), CurrRange = InvalidRange; DWARFUnit &OrigUnit = Unit.getOrigUnit(); auto OrigUnitDie = OrigUnit.getUnitDIE(false); - uint64_t OrigLowPc = - dwarf::toAddress(OrigUnitDie.find(dwarf::DW_AT_low_pc), -1ULL); + uint64_t OrigLowPc = dwarf::toAddress(OrigUnitDie.find(dwarf::DW_AT_low_pc), + std::numeric_limits::max()); // Ranges addresses are based on the unit's low_pc. Compute the // offset we need to apply to adapt to the new unit's low_pc. int64_t UnitPcOffset = 0; - if (OrigLowPc != -1ULL) + if (OrigLowPc != std::numeric_limits::max()) UnitPcOffset = int64_t(OrigLowPc) - Unit.getLowPc(); for (const auto &RangeAttribute : Unit.getRangesAttributes()) { @@ -1514,17 +1513,19 @@ const DWARFDebugRangeList::RangeListEntry &First = Entries.front(); if (CurrRange == InvalidRange || - First.StartAddress + OrigLowPc < CurrRange.start() || - First.StartAddress + OrigLowPc >= CurrRange.stop()) { - CurrRange = FunctionRanges.find(First.StartAddress + OrigLowPc); - if (CurrRange == InvalidRange || - CurrRange.start() > First.StartAddress + OrigLowPc) { + First.SectionIndex != CurrRange.start().SectionIndex || + First.StartAddress + OrigLowPc < CurrRange.start().Address || + First.StartAddress + OrigLowPc >= CurrRange.stop().Address) { + CurrRange = FunctionRanges.find( + {First.StartAddress + OrigLowPc, First.SectionIndex}); + if (CurrRange == InvalidRange || CurrRange == FunctionRanges.end() || + CurrRange.start().SectionIndex != First.SectionIndex || + CurrRange.start().Address > First.StartAddress + OrigLowPc) { reportWarning("no mapping for range.", File); continue; } } } - TheDwarfEmitter->emitRangesEntries(UnitPcOffset, OrigLowPc, CurrRange, Entries, AddressSize); } @@ -1644,37 +1645,43 @@ // it is marked as end_sequence in the input (because in that // case, the relocation offset is accurate and that entry won't // serve as the start of another function). - if (CurrRange == InvalidRange || Row.Address.Address < CurrRange.start() || - Row.Address.Address > CurrRange.stop() || - (Row.Address.Address == CurrRange.stop() && !Row.EndSequence)) { + if (CurrRange == InvalidRange || + Row.Address.SectionIndex != CurrRange.start().SectionIndex || + Row.Address.Address < CurrRange.start().Address || + Row.Address.Address > CurrRange.stop().Address || + (Row.Address.Address == CurrRange.stop().Address && !Row.EndSequence)) { // We just stepped out of a known range. Insert a end_sequence // corresponding to the end of the range. uint64_t StopAddress = CurrRange != InvalidRange - ? CurrRange.stop() + CurrRange.value() - : -1ULL; - CurrRange = FunctionRanges.find(Row.Address.Address); + ? CurrRange.stop().Address + CurrRange.value() + : std::numeric_limits::max(); + CurrRange = FunctionRanges.find(Row.Address); bool CurrRangeValid = - CurrRange != InvalidRange && CurrRange.start() <= Row.Address.Address; + CurrRange != InvalidRange && + CurrRange.start().SectionIndex == Row.Address.SectionIndex && + CurrRange.start().Address <= Row.Address.Address; if (!CurrRangeValid) { CurrRange = InvalidRange; - if (StopAddress != -1ULL) { + if (StopAddress != std::numeric_limits::max()) { // Try harder by looking in the Address ranges map. // There are corner cases where this finds a // valid entry. It's unclear if this is right or wrong, but // for now do as dsymutil. // FIXME: Understand exactly what cases this addresses and // potentially remove it along with the Ranges map. - auto Range = Ranges.lower_bound(Row.Address.Address); + auto Range = Ranges.lower_bound(Row.Address); if (Range != Ranges.begin() && Range != Ranges.end()) --Range; - if (Range != Ranges.end() && Range->first <= Row.Address.Address && + if (Range != Ranges.end() && + Range->first.SectionIndex == Row.Address.SectionIndex && + Range->first.Address <= Row.Address.Address && Range->second.HighPC >= Row.Address.Address) { StopAddress = Row.Address.Address + Range->second.Offset; } } } - if (StopAddress != -1ULL && !Seq.empty()) { + if (StopAddress != std::numeric_limits::max() && !Seq.empty()) { // Insert end sequence row with the computed end address, but // the same line as the previous one. auto NextLine = Seq.back(); @@ -1739,6 +1746,9 @@ case AccelTableKind::Dwarf: emitDwarfAcceleratorEntriesForUnit(Unit); break; + case AccelTableKind::None: + // nothing to do + break; case AccelTableKind::Default: llvm_unreachable("The default must be updated to a concrete value."); break; @@ -1827,10 +1837,11 @@ // the function entry point, thus we can't just lookup the address // in the debug map. Use the AddressInfo's range map to see if the FDE // describes something that we can relocate. - auto Range = Ranges.upper_bound(Loc); + auto Range = + Ranges.upper_bound({Loc, object::SectionedAddress::UndefSection}); if (Range != Ranges.begin()) --Range; - if (Range == Ranges.end() || Range->first > Loc || + if (Range == Ranges.end() || Range->first.Address > Loc || Range->second.HighPC <= Loc) { // The +4 is to account for the size of the InitialLength field itself. InputOffset = EntryOffset + InitialLength + 4; @@ -2491,6 +2502,9 @@ case AccelTableKind::Dwarf: TheDwarfEmitter->emitDebugNames(DebugNames); break; + case AccelTableKind::None: + // nothing to do + break; case AccelTableKind::Default: llvm_unreachable("Default should have already been resolved."); break; diff --git a/llvm/lib/DWARFLinker/DWARFLinkerCompileUnit.cpp b/llvm/lib/DWARFLinker/DWARFLinkerCompileUnit.cpp --- a/llvm/lib/DWARFLinker/DWARFLinkerCompileUnit.cpp +++ b/llvm/lib/DWARFLinker/DWARFLinkerCompileUnit.cpp @@ -11,6 +11,27 @@ namespace llvm { +object::SectionedAddress DenseMapInfo::getEmptyKey() { + return {object::SectionedAddress::UndefSection, + object::SectionedAddress::UndefSection - 1}; +} + +object::SectionedAddress +DenseMapInfo::getTombstoneKey() { + return {object::SectionedAddress::UndefSection, + object::SectionedAddress::UndefSection - 2}; +} + +unsigned DenseMapInfo::getHashValue( + const object::SectionedAddress &val) { + return llvm::hash_value(std::make_pair(val.Address, val.SectionIndex)); +} + +bool DenseMapInfo::isEqual( + const object::SectionedAddress &lhs, const object::SectionedAddress &rhs) { + return lhs == rhs; +} + /// Check if the DIE at \p Idx is in the scope of a function. static bool inFunctionScope(CompileUnit &U, unsigned Idx) { while (Idx) { @@ -99,19 +120,23 @@ } } -void CompileUnit::addLabelLowPc(uint64_t LabelLowPc, int64_t PcOffset) { +void CompileUnit::addLabelLowPc(object::SectionedAddress LabelLowPc, + int64_t PcOffset) { Labels.insert({LabelLowPc, PcOffset}); } -void CompileUnit::addFunctionRange(uint64_t FuncLowPc, uint64_t FuncHighPc, +void CompileUnit::addFunctionRange(object::SectionedAddress FuncLowPc, + object::SectionedAddress FuncHighPc, int64_t PcOffset) { + assert(FuncLowPc.SectionIndex == FuncHighPc.SectionIndex); + // Don't add empty ranges to the interval map. They are a problem because // the interval map expects half open intervals. This is safe because they // are empty anyway. if (FuncHighPc != FuncLowPc) Ranges.insert(FuncLowPc, FuncHighPc, PcOffset); - this->LowPc = std::min(LowPc, FuncLowPc + PcOffset); - this->HighPc = std::max(HighPc, FuncHighPc + PcOffset); + LowPc = std::min(LowPc, FuncLowPc.Address + PcOffset); + HighPc = std::max(HighPc, FuncHighPc.Address + PcOffset); } void CompileUnit::noteRangeAttribute(const DIE &Die, PatchLocation Attr) { diff --git a/llvm/lib/DWARFLinker/DWARFStreamer.cpp b/llvm/lib/DWARFLinker/DWARFStreamer.cpp --- a/llvm/lib/DWARFLinker/DWARFStreamer.cpp +++ b/llvm/lib/DWARFLinker/DWARFStreamer.cpp @@ -49,6 +49,7 @@ MAI.reset(TheTarget->createMCAsmInfo(*MRI, TripleName, MCOptions)); if (!MAI) return error("no asm info for target " + TripleName, Context), false; + MAI->setDwarfUseRelocationsAcrossSections(false); MOFI.reset(new MCObjectFileInfo); MC.reset(new MCContext(MAI.get(), MRI.get(), MOFI.get())); @@ -102,6 +103,8 @@ Asm.reset(TheTarget->createAsmPrinter(*TM, std::unique_ptr(MS))); if (!Asm) return error("no asm printer for target " + TripleName, Context), false; + const_cast(Asm->MAI)->setDwarfUseRelocationsAcrossSections( + false); RangesSectionSize = 0; LocSectionSize = 0; @@ -300,8 +303,9 @@ continue; // All range entries should lie in the function range. - if (!(Range.StartAddress + OrigLowPc >= FuncRange.start() && - Range.EndAddress + OrigLowPc <= FuncRange.stop())) + if (!(Range.SectionIndex == FuncRange.start().SectionIndex && + Range.StartAddress + OrigLowPc >= FuncRange.start().Address && + Range.EndAddress + OrigLowPc <= FuncRange.stop().Address)) warn("inconsistent range data.", "emitting debug_ranges"); MS->emitIntValue(Range.StartAddress + PcOffset, AddressSize); MS->emitIntValue(Range.EndAddress + PcOffset, AddressSize); @@ -329,8 +333,8 @@ const auto &FunctionRanges = Unit.getFunctionRanges(); for (auto Range = FunctionRanges.begin(), End = FunctionRanges.end(); Range != End; ++Range) - Ranges.push_back(std::make_pair(Range.start() + Range.value(), - Range.stop() + Range.value())); + Ranges.push_back(std::make_pair(Range.start().Address + Range.value(), + Range.stop().Address + Range.value())); // The object addresses where sorted, but again, the linked // addresses might end up in a different order. @@ -381,7 +385,8 @@ MS->SwitchSection(MC->getObjectFileInfo()->getDwarfRangesSection()); // Offset each range by the right amount. - int64_t PcOffset = -Unit.getLowPc(); + int64_t PcOffset = 0; + PcOffset = -Unit.getLowPc(); // Emit coalesced ranges. for (auto Range = Ranges.begin(), End = Ranges.end(); Range != End; ++Range) { MS->emitIntValue(Range->first + PcOffset, AddressSize); diff --git a/llvm/lib/DebugInfo/DWARF/DWARFContext.cpp b/llvm/lib/DebugInfo/DWARF/DWARFContext.cpp --- a/llvm/lib/DebugInfo/DWARF/DWARFContext.cpp +++ b/llvm/lib/DebugInfo/DWARF/DWARFContext.cpp @@ -936,11 +936,7 @@ if (!Line) Line.reset(new DWARFDebugLine); - auto UnitDIE = U->getUnitDIE(); - if (!UnitDIE) - return nullptr; - - auto Offset = toSectionOffset(UnitDIE.find(DW_AT_stmt_list)); + auto Offset = U->getStmtListOffset(); if (!Offset) return nullptr; // No line table for this compile unit. diff --git a/llvm/lib/DebugInfo/DWARF/DWARFDie.cpp b/llvm/lib/DebugInfo/DWARF/DWARFDie.cpp --- a/llvm/lib/DebugInfo/DWARF/DWARFDie.cpp +++ b/llvm/lib/DebugInfo/DWARF/DWARFDie.cpp @@ -386,10 +386,10 @@ if (auto Value = Die.find(Attrs)) return Value; - if (auto D = Die.getAttributeValueAsReferencedDie(DW_AT_abstract_origin)) + if (auto D = Die.getAttributeValueAsReferencedDie(DW_AT_abstract_origin)) { if (Seen.insert(D).second) Worklist.push_back(D); - + } if (auto D = Die.getAttributeValueAsReferencedDie(DW_AT_specification)) if (Seen.insert(D).second) Worklist.push_back(D); diff --git a/llvm/lib/DebugInfo/DWARF/DWARFUnit.cpp b/llvm/lib/DebugInfo/DWARF/DWARFUnit.cpp --- a/llvm/lib/DebugInfo/DWARF/DWARFUnit.cpp +++ b/llvm/lib/DebugInfo/DWARF/DWARFUnit.cpp @@ -370,8 +370,19 @@ DWO.reset(); } +Optional DWARFUnit::getStmtListOffset() { + if (!StmtListOffset) + StmtListOffset = toSectionOffset(getUnitDIE().find(DW_AT_stmt_list)); + + return StmtListOffset; +} + const char *DWARFUnit::getCompilationDir() { - return dwarf::toString(getUnitDIE().find(DW_AT_comp_dir), nullptr); + if (!CompilationDir) + CompilationDir = + dwarf::toString(getUnitDIE().find(DW_AT_comp_dir), nullptr); + + return CompilationDir; } void DWARFUnit::extractDIEsToVector( diff --git a/llvm/lib/Support/StringMap.cpp b/llvm/lib/Support/StringMap.cpp --- a/llvm/lib/Support/StringMap.cpp +++ b/llvm/lib/Support/StringMap.cpp @@ -77,6 +77,7 @@ init(16); HTSize = NumBuckets; } + unsigned FullHashValue = djbHash(Name, 0); unsigned BucketNo = FullHashValue & (HTSize - 1); unsigned *HashTable = (unsigned *)(TheTable + NumBuckets + 1); diff --git a/llvm/tools/dsymutil/DwarfLinkerForBinary.h b/llvm/tools/dsymutil/DwarfLinkerForBinary.h --- a/llvm/tools/dsymutil/DwarfLinkerForBinary.h +++ b/llvm/tools/dsymutil/DwarfLinkerForBinary.h @@ -112,15 +112,14 @@ for (const auto &Entry : DMO.symbols()) { const auto &Mapping = Entry.getValue(); if (Mapping.Size && Mapping.ObjectAddress) - AddressRanges[*Mapping.ObjectAddress] = ObjFileAddressRange( + AddressRanges[{*Mapping.ObjectAddress, + object::SectionedAddress::UndefSection}] = { *Mapping.ObjectAddress + Mapping.Size, - int64_t(Mapping.BinaryAddress) - *Mapping.ObjectAddress); + int64_t(Mapping.BinaryAddress - *Mapping.ObjectAddress)}; } } virtual ~AddressManager() override { clear(); } - virtual bool areRelocationsResolved() const override { return true; } - bool hasValidRelocs(bool ResetRelocsPtr = true) override { if (ResetRelocsPtr) NextValidReloc = 0; diff --git a/llvm/tools/dsymutil/DwarfLinkerForBinary.cpp b/llvm/tools/dsymutil/DwarfLinkerForBinary.cpp --- a/llvm/tools/dsymutil/DwarfLinkerForBinary.cpp +++ b/llvm/tools/dsymutil/DwarfLinkerForBinary.cpp @@ -659,7 +659,6 @@ /// \returns whether any reloc has been applied. bool DwarfLinkerForBinary::AddressManager::applyValidRelocs( MutableArrayRef Data, uint64_t BaseOffset, bool IsLittleEndian) { - assert(areRelocationsResolved()); assert((NextValidReloc == 0 || BaseOffset > ValidRelocs[NextValidReloc - 1].Offset) && "BaseOffset should only be increasing."); diff --git a/llvm/unittests/DebugInfo/DWARF/CMakeLists.txt b/llvm/unittests/DebugInfo/DWARF/CMakeLists.txt --- a/llvm/unittests/DebugInfo/DWARF/CMakeLists.txt +++ b/llvm/unittests/DebugInfo/DWARF/CMakeLists.txt @@ -3,6 +3,7 @@ AsmPrinter BinaryFormat DebugInfoDWARF + DWARFLinker MC Object ObjectYAML @@ -23,6 +24,7 @@ DWARFFormValueTest.cpp DWARFListTableTest.cpp DWARFLocationExpressionTest.cpp + DWARFLinkerCompileUnit.cpp ) target_link_libraries(DebugInfoDWARFTests PRIVATE LLVMTestingSupport) diff --git a/llvm/unittests/DebugInfo/DWARF/DWARFLinkerCompileUnit.cpp b/llvm/unittests/DebugInfo/DWARF/DWARFLinkerCompileUnit.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/DebugInfo/DWARF/DWARFLinkerCompileUnit.cpp @@ -0,0 +1,160 @@ +//===- llvm/unittest/DebugInfo/DWARFLinkerCompileUnit.cpp -----------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/DWARFLinker/DWARFLinkerCompileUnit.h" +#include "llvm/BinaryFormat/Dwarf.h" +#include "llvm/DebugInfo/DWARF/DWARFContext.h" +#include "llvm/ObjectYAML/DWARFEmitter.h" +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::dwarf; +using object::SectionedAddress; + +namespace { + +TEST(DWARFLinkerCompileUnit, sectionedAddressRanges) { + const char *yamldata = "debug_abbrev:\n" + " - Table:\n" + " - Code: 0x00000001\n" + " Tag: DW_TAG_compile_unit\n" + " Children: DW_CHILDREN_yes\n" + "debug_info:\n" + " - Version: 4\n" + " AddrSize: 8\n" + " Entries:\n" + " - AbbrCode: 0x00000001\n" + " - AbbrCode: 0x00000000\n"; + + auto ErrOrSections = DWARFYAML::emitDebugSections(StringRef(yamldata), true); + ASSERT_TRUE((bool)ErrOrSections); + std::unique_ptr DwarfContext = + DWARFContext::create(*ErrOrSections, 8); + + // Verify the number of compile units is correct. + uint32_t NumCUs = DwarfContext->getNumCompileUnits(); + EXPECT_EQ(NumCUs, 1u); + DWARFCompileUnit *U = cast(DwarfContext->getUnitAtIndex(0)); + + // Check the compile unit DIE is valid. + auto DieDG = U->getUnitDIE(false); + EXPECT_TRUE(DieDG.isValid()); + + // Create DWARFLinker compile unit. + CompileUnit CU(*U, 1, false, ""); + + // Check for adding zero address range from undefined section. + CU.addFunctionRange({0x10, object::SectionedAddress::UndefSection}, + {0x10, object::SectionedAddress::UndefSection}, 0x4000); + EXPECT_TRUE(CU.getFunctionRanges().empty()); + + EXPECT_TRUE(CU.getLowPc() == 0x4010); + EXPECT_TRUE(CU.getHighPc() == 0x4010); + + // Check for adding zero address range from first section. + CU.addFunctionRange({0x10, 1}, {0x10, 1}, 0x4500); + EXPECT_TRUE(CU.getFunctionRanges().empty()); + + EXPECT_TRUE(CU.getLowPc() == 0x4010); + EXPECT_TRUE(CU.getHighPc() == 0x4510); + + // Check for adding valid address range from undefined section. + CU.addFunctionRange({0x10, object::SectionedAddress::UndefSection}, + {0x20, object::SectionedAddress::UndefSection}, 0x4000); + EXPECT_TRUE(!CU.getFunctionRanges().empty()); + EXPECT_TRUE(CU.getFunctionRanges().find( + {0x10, object::SectionedAddress::UndefSection}) != + CU.getFunctionRanges().end()); + EXPECT_TRUE(CU.getFunctionRanges().lookup( + {0x10, object::SectionedAddress::UndefSection}) == 0x4000); + + // Check for adding second valid address range from undefined section. + CU.addFunctionRange({0x20, object::SectionedAddress::UndefSection}, + {0x40, object::SectionedAddress::UndefSection}, 0x5000); + EXPECT_TRUE(!CU.getFunctionRanges().empty()); + EXPECT_TRUE(CU.getFunctionRanges().find( + {0x20, object::SectionedAddress::UndefSection}) != + CU.getFunctionRanges().end()); + EXPECT_TRUE(CU.getFunctionRanges().lookup( + {0x20, object::SectionedAddress::UndefSection}) == 0x5000); + EXPECT_TRUE(CU.getFunctionRanges().lookup( + {0x19, object::SectionedAddress::UndefSection}) == 0x4000); + + // Check for adding third valid address range from undefined section. + CU.addFunctionRange({0x100, object::SectionedAddress::UndefSection}, + {0x200, object::SectionedAddress::UndefSection}, 0x6000); + EXPECT_TRUE(!CU.getFunctionRanges().empty()); + EXPECT_TRUE(CU.getFunctionRanges().find( + {0x100, object::SectionedAddress::UndefSection}) != + CU.getFunctionRanges().end()); + + // Check that function ranges does not contain values which were not put there + // previously. + EXPECT_TRUE(CU.getFunctionRanges().lookup( + {0x5, object::SectionedAddress::UndefSection}, -1LL) == -1LL); + EXPECT_TRUE(CU.getFunctionRanges().lookup( + {0x100, object::SectionedAddress::UndefSection}) == 0x6000); + + // Check compile unit low/high pc values. + EXPECT_TRUE(CU.getLowPc() == 0x4010); + EXPECT_TRUE(CU.getHighPc() == 0x6200); + + // Check for adding valid address range from first section. + CU.addFunctionRange({0x10, 1}, {0x20, 1}, 0x1000); + EXPECT_TRUE(!CU.getFunctionRanges().empty()); + EXPECT_TRUE(CU.getFunctionRanges().find({0x10, 1}) != + CU.getFunctionRanges().end()); + EXPECT_TRUE(CU.getFunctionRanges().lookup({0x10, 1}) == 0x1000); + + // Check for adding second valid address range from first section. + CU.addFunctionRange({0x20, 1}, {0x40, 1}, 0x2000); + EXPECT_TRUE(!CU.getFunctionRanges().empty()); + EXPECT_TRUE(CU.getFunctionRanges().find({0x20, 1}) != + CU.getFunctionRanges().end()); + EXPECT_TRUE(CU.getFunctionRanges().lookup({0x20, 1}) == 0x2000); + EXPECT_TRUE(CU.getFunctionRanges().lookup({0x19, 1}) == 0x1000); + + // Check for adding valid address range from second section. + CU.addFunctionRange({0x10, 2}, {0x20, 2}, 0x3000); + EXPECT_TRUE(!CU.getFunctionRanges().empty()); + EXPECT_TRUE(CU.getFunctionRanges().find({0x10, 2}) != + CU.getFunctionRanges().end()); + EXPECT_TRUE(CU.getFunctionRanges().lookup({0x10, 2}) == 0x3000); + + // Check that function ranges does not contain values which were not put there + // previously. + EXPECT_TRUE(CU.getFunctionRanges().lookup({0x10, 3}, -1LL) == -1LL); + EXPECT_TRUE(CU.getFunctionRanges().lookup({UINT64_MAX, 2}, -1LL) == -1LL); + + EXPECT_TRUE(CU.getLowPc() == 0x1010); + EXPECT_TRUE(CU.getHighPc() == 0x6200); + + // Check for adding valid address range from four section. + CU.addFunctionRange({0x0, 4}, {0x200, 4}, 0x8000); + EXPECT_TRUE(!CU.getFunctionRanges().empty()); + EXPECT_TRUE(CU.getFunctionRanges().find({0x10, 4}) != + CU.getFunctionRanges().end()); + EXPECT_TRUE(CU.getFunctionRanges().lookup({0x10, 4}) == 0x8000); + + EXPECT_TRUE(CU.getLowPc() == 0x1010); + EXPECT_TRUE(CU.getHighPc() == 0x8200); + + // Check adding labels. + CU.addLabelLowPc({0x10, object::SectionedAddress::UndefSection}, 0x4000); + EXPECT_TRUE(CU.hasLabelAt({0x10, object::SectionedAddress::UndefSection})); + EXPECT_TRUE(!CU.hasLabelAt({0x0, object::SectionedAddress::UndefSection})); + + CU.addLabelLowPc({0x10, 1}, 0x4000); + EXPECT_TRUE(CU.hasLabelAt({0x10, 1})); + EXPECT_TRUE(!CU.hasLabelAt({0x0, 1})); + + EXPECT_TRUE(!CU.hasLabelAt({0x10, 2})); +} + +} // end anonymous namespace