diff options
26 files changed, 274 insertions, 55 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 5dda38c..bb112da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,17 @@ set(CMAKE_CXX_STANDARD 20) add_compile_options(-fstrict-enums -Wall -Wextra) +set(MINIMUM_GNU_VERSION 11) +set(MINIMUM_Clang_VERSION 15) + +# Note, the quotes around the variable are significant. If we use a compiler +# that does not resolve to a definition above, the empty string corresponds to +# a version where all components are omitted and hence treated as zero. +if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${MINIMUM_${CMAKE_CXX_COMPILER_ID}_VERSION}") + message(FATAL_ERROR "Unsupported Compiler Version!\n" + "Need at least ${CMAKE_CXX_COMPILER_ID} ${MINIMUM_${CMAKE_CXX_COMPILER_ID}_VERSION}") +endif() + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") # GCC has problems detecting "no-return" switches and destructors. add_compile_options(-Wno-return-type) @@ -37,7 +37,7 @@ Instructions are included for local and Docker builds. ### Dependencies -STG is written in C++20. It is known to compile with GCC 11, Clang 14 or +STG is written in C++20. It is known to compile with GCC 11, Clang 15 or later versions. | *Dependency* | *Debian* | *RedHat* | @@ -98,7 +98,7 @@ There are two types of filters that can be applied to STG output: File filters are only applicable to ELF binary objects containing DWARF with source location information; any other kind of input will be unaffected. -2. `-S|--symbols|--symbol-filter <filter>` +1. `-S|--symbols|--symbol-filter <filter>` Filter ELF symbols by name (which may include a `@version` or `@@version` suffix). diff --git a/dwarf_processor.cc b/dwarf_processor.cc index a8e41bf..48d6e26 100644 --- a/dwarf_processor.cc +++ b/dwarf_processor.cc @@ -301,6 +301,17 @@ class Processor { private: void Process(Entry& entry) { + try { + return ProcessInternal(entry); + } catch (Exception& e) { + std::ostringstream os; + os << "processing DIE " << Hex(entry.GetOffset()); + e.Add(os.str()); + throw; + } + } + + void ProcessInternal(Entry& entry) { ++result_.processed_entries; auto tag = entry.GetTag(); switch (tag) { @@ -424,6 +435,11 @@ class Processor { const std::string type_name = scope_ + GetName(entry); auto referred_type_id = GetIdForReferredType(MaybeGetReferredType(entry)); const Id id = AddProcessedNode<Typedef>(entry, type_name, referred_type_id); + if (!ShouldKeepDefinition(entry, type_name)) { + // We always model (and keep) typedef definitions. But we should exclude + // filtered out types from being type roots. + return; + } AddNamedTypeNode(id); } diff --git a/dwarf_wrappers.cc b/dwarf_wrappers.cc index e5780eb..d13d7b8 100644 --- a/dwarf_wrappers.cc +++ b/dwarf_wrappers.cc @@ -129,13 +129,19 @@ struct Expression { size_t length = 0; }; -Expression GetExpression(Dwarf_Attribute& attribute) { +std::optional<Expression> MaybeGetExpression(Dwarf_Attribute& attribute) { Expression result; Check(dwarf_getlocation(&attribute, &result.atoms, &result.length) == kReturnOk) << "dwarf_getlocation returned error"; - Check(result.atoms != nullptr && result.length > 0) - << "dwarf_getlocation returned empty expression"; + // If no location attribute is present or has an empty location description, + // the variable is present in the source but not in the object code. + // So zero length expression is equivalent of no location attribute. + if (result.length == 0) { + return std::nullopt; + } + Check(result.atoms != nullptr) + << "dwarf_getlocation returned non-empty expression with NULL atoms"; return result; } @@ -298,7 +304,11 @@ std::optional<Entry> Entry::MaybeGetReference(uint32_t attribute) { namespace { std::optional<Address> GetAddressFromLocation(Dwarf_Attribute& attribute) { - const auto expression = GetExpression(attribute); + const auto expression_opt = MaybeGetExpression(attribute); + if (!expression_opt) { + return {}; + } + const Expression& expression = *expression_opt; Dwarf_Attribute result_attribute; if (dwarf_getlocation_attr(&attribute, expression.atoms, &result_attribute) == @@ -360,7 +370,11 @@ std::optional<uint64_t> Entry::MaybeGetMemberByteOffset() { } // Parse location expression - const auto expression = GetExpression(attribute.value()); + const auto expression_opt = MaybeGetExpression(attribute.value()); + if (!expression_opt) { + return {}; + } + const Expression& expression = *expression_opt; // Parse virtual base classes offset, which looks like this: // [0] = DW_OP_dup @@ -394,7 +408,11 @@ std::optional<uint64_t> Entry::MaybeGetVtableOffset() { } // Parse location expression - const Expression expression = GetExpression(attribute.value()); + const auto expression_opt = MaybeGetExpression(attribute.value()); + if (!expression_opt) { + return {}; + } + const Expression& expression = *expression_opt; // We expect compilers to produce expression with one constant operand if (expression.length == 1) { @@ -32,14 +32,20 @@ namespace stg { class Exception : public std::exception { public: - explicit Exception(const std::string& message) : message_(message) {} + explicit Exception(const std::string& message) { + Add(message); + } const char* what() const noexcept(true) final { return message_.c_str(); } + void Add(const std::string& message) { + (message_ += message) += '\n'; + } + private: - const std::string message_; + std::string message_; }; class Check { @@ -157,7 +157,7 @@ class SetFilter : public Filter { const Items items_; }; -static const char* kTokenCharacters = ":!()&|"; +const char* kTokenCharacters = ":!()&|"; // Split a filter expression into tokens. // diff --git a/filter_test.cc b/filter_test.cc index 7666965..2be2bcc 100644 --- a/filter_test.cc +++ b/filter_test.cc @@ -19,7 +19,6 @@ #include "filter.h" -#include <sstream> #include <string> #include <tuple> #include <vector> @@ -52,7 +51,6 @@ TEST_CASE("bad syntax cases") { }; for (const auto& expression : cases) { - std::ostringstream os; GIVEN("filter: " + expression) { CHECK_THROWS(stg::MakeFilter(expression)); } @@ -104,7 +102,6 @@ TEST_CASE("hand-curated cases") { }; for (const auto& [expression, ins, outs] : cases) { - std::ostringstream os; GIVEN("filter: " + expression) { auto filter = stg::MakeFilter(expression); for (const auto& in : ins) { @@ -20,10 +20,12 @@ #include "input.h" #include <memory> +#include <sstream> #include "abigail_reader.h" #include "btf_reader.h" #include "elf_reader.h" +#include "error.h" #include "filter.h" #include "graph.h" #include "metrics.h" @@ -32,9 +34,11 @@ namespace stg { -Id Read(Graph& graph, InputFormat format, const char* input, - ReadOptions options, const std::unique_ptr<Filter>& file_filter, - Metrics& metrics) { +namespace { + +Id ReadInternal(Graph& graph, InputFormat format, const char* input, + ReadOptions options, const std::unique_ptr<Filter>& file_filter, + Metrics& metrics) { switch (format) { case InputFormat::ABI: { Time read(metrics, "read ABI"); @@ -55,4 +59,19 @@ Id Read(Graph& graph, InputFormat format, const char* input, } } +} // namespace + +Id Read(Graph& graph, InputFormat format, const char* input, + ReadOptions options, const std::unique_ptr<Filter>& file_filter, + Metrics& metrics) { + try { + return ReadInternal(graph, format, input, options, file_filter, metrics); + } catch (Exception& e) { + std::ostringstream os; + os << "processing file '" << input << '\''; + e.Add(os.str()); + throw; + } +} + } // namespace stg @@ -123,7 +123,7 @@ int main(int argc, char* argv[]) { std::unique_ptr<stg::Filter> opt_symbol_filter; stg::ReadOptions opt_read_options; stg::InputFormat opt_input_format = stg::InputFormat::ABI; - std::vector<const char*> inputs; + std::vector<std::pair<stg::InputFormat, const char*>> inputs; std::vector<const char*> outputs; static option opts[] = { {"metrics", no_argument, nullptr, 'm'}, @@ -189,7 +189,7 @@ int main(int argc, char* argv[]) { opt_input_format = stg::InputFormat::STG; break; case 1: - inputs.push_back(argument); + inputs.emplace_back(opt_input_format, argument); break; case 'o': if (strcmp(argument, "-") == 0) { @@ -207,10 +207,9 @@ int main(int argc, char* argv[]) { stg::Metrics metrics; std::vector<stg::Id> roots; roots.reserve(inputs.size()); - for (auto input : inputs) { - roots.push_back(stg::Read(graph, opt_input_format, input, - opt_read_options, opt_file_filter, - metrics)); + for (auto& [format, input] : inputs) { + roots.push_back(stg::Read(graph, format, input, opt_read_options, + opt_file_filter, metrics)); } stg::Id root = roots.size() == 1 ? roots[0] : stg::Merge(graph, roots, metrics); @@ -235,7 +234,7 @@ int main(int argc, char* argv[]) { } return 0; } catch (const stg::Exception& e) { - std::cerr << e.what() << '\n'; + std::cerr << e.what(); return 1; } } @@ -270,7 +270,7 @@ int main(int argc, char* argv[]) { } return status; } catch (const stg::Exception& e) { - std::cerr << e.what() << '\n'; + std::cerr << e.what(); return 1; } } diff --git a/test_cases/diff_tests/member/expected/pointer_to_member_cc.o_o_flat b/test_cases/diff_tests/member/expected/pointer_to_member_cc.o_o_flat index 73846d7..59e5a0e 100644 --- a/test_cases/diff_tests/member/expected/pointer_to_member_cc.o_o_flat +++ b/test_cases/diff_tests/member/expected/pointer_to_member_cc.o_o_flat @@ -1,5 +1,7 @@ function symbol 'int struct S::* s2()' {_Z2s2v} was added +function symbol 'int s10(int struct S::*)' {_Z3s10M1Si} was added + function symbol 'void pmz_fun()' {_Z7pmz_funv} was added variable symbol 'char struct Y::* pmc' was added diff --git a/test_cases/diff_tests/member/expected/pointer_to_member_cc.o_o_plain b/test_cases/diff_tests/member/expected/pointer_to_member_cc.o_o_plain index 73846d7..59e5a0e 100644 --- a/test_cases/diff_tests/member/expected/pointer_to_member_cc.o_o_plain +++ b/test_cases/diff_tests/member/expected/pointer_to_member_cc.o_o_plain @@ -1,5 +1,7 @@ function symbol 'int struct S::* s2()' {_Z2s2v} was added +function symbol 'int s10(int struct S::*)' {_Z3s10M1Si} was added + function symbol 'void pmz_fun()' {_Z7pmz_funv} was added variable symbol 'char struct Y::* pmc' was added diff --git a/test_cases/diff_tests/member/expected/pointer_to_member_cc.o_o_small b/test_cases/diff_tests/member/expected/pointer_to_member_cc.o_o_small index 73846d7..59e5a0e 100644 --- a/test_cases/diff_tests/member/expected/pointer_to_member_cc.o_o_small +++ b/test_cases/diff_tests/member/expected/pointer_to_member_cc.o_o_small @@ -1,5 +1,7 @@ function symbol 'int struct S::* s2()' {_Z2s2v} was added +function symbol 'int s10(int struct S::*)' {_Z3s10M1Si} was added + function symbol 'void pmz_fun()' {_Z7pmz_funv} was added variable symbol 'char struct Y::* pmc' was added diff --git a/test_cases/diff_tests/member/expected/pointer_to_member_cc.o_o_viz b/test_cases/diff_tests/member/expected/pointer_to_member_cc.o_o_viz index 31262c8..292ca80 100644 --- a/test_cases/diff_tests/member/expected/pointer_to_member_cc.o_o_viz +++ b/test_cases/diff_tests/member/expected/pointer_to_member_cc.o_o_viz @@ -2,46 +2,48 @@ digraph "ABI diff" { "0" [shape=rectangle, label="'interface'"] "1" [color=red, label="added(int struct S::* s2() {_Z2s2v})"] "0" -> "1" [label=""] - "2" [color=red, label="added(void pmz_fun() {_Z7pmz_funv})"] + "2" [color=red, label="added(int s10(int struct S::*) {_Z3s10M1Si})"] "0" -> "2" [label=""] - "3" [color=red, label="added(char struct Y::* pmc)"] + "3" [color=red, label="added(void pmz_fun() {_Z7pmz_funv})"] "0" -> "3" [label=""] - "4" [color=red, label="added(int union U::* pmcu)"] + "4" [color=red, label="added(char struct Y::* pmc)"] "0" -> "4" [label=""] - "5" [color=red, label="added(double struct X::* pmd)"] + "5" [color=red, label="added(int union U::* pmcu)"] "0" -> "5" [label=""] - "6" [color=red, label="added(void(struct X::* pmf)(struct X*, int))"] + "6" [color=red, label="added(double struct X::* pmd)"] "0" -> "6" [label=""] - "7" [color=red, label="added(int struct X::* pmi)"] + "7" [color=red, label="added(void(struct X::* pmf)(struct X*, int))"] "0" -> "7" [label=""] - "8" [color=red, label="added(int union U::* pmu)"] + "8" [color=red, label="added(int struct X::* pmi)"] "0" -> "8" [label=""] - "9" [color=red, label="added(int struct { int t; }::* pmy)"] + "9" [color=red, label="added(int union U::* pmu)"] "0" -> "9" [label=""] - "10" [color=red, label="added(int struct S::* s0)"] + "10" [color=red, label="added(int struct { int t; }::* pmy)"] "0" -> "10" [label=""] - "11" [color=red, label="added(int struct S::** s1)"] + "11" [color=red, label="added(int struct S::* s0)"] "0" -> "11" [label=""] - "12" [color=red, label="added(int struct S::*(* s3)())"] + "12" [color=red, label="added(int struct S::** s1)"] "0" -> "12" [label=""] - "13" [color=red, label="added(int struct S::* s4[7])"] + "13" [color=red, label="added(int struct S::*(* s3)())"] "0" -> "13" [label=""] - "14" [color=red, label="added(int* struct S::* s5)"] + "14" [color=red, label="added(int struct S::* s4[7])"] "0" -> "14" [label=""] - "15" [color=red, label="added(int(* struct S::* s6)())"] + "15" [color=red, label="added(int* struct S::* s5)"] "0" -> "15" [label=""] - "16" [color=red, label="added(int(struct S::* s7)(struct S*))"] + "16" [color=red, label="added(int(* struct S::* s6)())"] "0" -> "16" [label=""] - "17" [color=red, label="added(int(struct S::* s8)[7])"] + "17" [color=red, label="added(int(struct S::* s7)(struct S*))"] "0" -> "17" [label=""] - "18" [color=red, label="added(const int struct S::* volatile s9)"] + "18" [color=red, label="added(int(struct S::* s8)[7])"] "0" -> "18" [label=""] - "19" [label="'char struct A::* diff' -> 'int struct B::* diff'"] - "20" [label="'char struct A::*' -> 'int struct B::*'"] - "21" [color=red, label="'struct A' -> 'struct B'"] - "20" -> "21" [label="containing"] - "22" [color=red, label="'char' -> 'int'"] - "20" -> "22" [label=""] - "19" -> "20" [label=""] + "19" [color=red, label="added(const int struct S::* volatile s9)"] "0" -> "19" [label=""] + "20" [label="'char struct A::* diff' -> 'int struct B::* diff'"] + "21" [label="'char struct A::*' -> 'int struct B::*'"] + "22" [color=red, label="'struct A' -> 'struct B'"] + "21" -> "22" [label="containing"] + "23" [color=red, label="'char' -> 'int'"] + "21" -> "23" [label=""] + "20" -> "21" [label=""] + "0" -> "20" [label=""] } diff --git a/test_cases/diff_tests/member/pointer_to_member.1.cc b/test_cases/diff_tests/member/pointer_to_member.1.cc index 61011ee..9779061 100644 --- a/test_cases/diff_tests/member/pointer_to_member.1.cc +++ b/test_cases/diff_tests/member/pointer_to_member.1.cc @@ -21,6 +21,9 @@ int (S::*s7)(); int (S::*s8)[7]; // declare s9 as volatile pointer to member of class S const int const int S::* volatile s9; +// declare s10 as function (pointer to member of class S int) returning int +int s10(int S::*); +int s10(int S::*) { return 0; } struct X { void f(int); @@ -48,8 +51,6 @@ auto pmu = &U::u; typedef const U CU; auto pmcu = &CU::u; -// TODO: everything above here should be an info test - struct B { int x; }; diff --git a/test_cases/info_tests/member/expected/pointer_to_member_cc.elf_stg b/test_cases/info_tests/member/expected/pointer_to_member_cc.elf_stg index 7b197b6..6d9e1d4 100644 --- a/test_cases/info_tests/member/expected/pointer_to_member_cc.elf_stg +++ b/test_cases/info_tests/member/expected/pointer_to_member_cc.elf_stg @@ -206,6 +206,11 @@ function { id: 0x9d80e32f return_type_id: 0x6720d32f } +function { + id: 0xa875fb6e + return_type_id: 0x6720d32f + parameter_id: 0xd7d46104 +} elf_symbol { id: 0xba0e5cd0 name: "_Z2s2v" @@ -215,6 +220,14 @@ elf_symbol { full_name: "s2" } elf_symbol { + id: 0x94e5eb64 + name: "_Z3s10M1Si" + is_defined: true + symbol_type: FUNCTION + type_id: 0xa875fb6e + full_name: "s10" +} +elf_symbol { id: 0x13e4cc52 name: "_Z7pmz_funv" is_defined: true @@ -353,6 +366,7 @@ elf_symbol { interface { id: 0x84ea5130 symbol_id: 0xba0e5cd0 + symbol_id: 0x94e5eb64 symbol_id: 0x13e4cc52 symbol_id: 0x648621f2 symbol_id: 0xff819903 diff --git a/test_cases/info_tests/member/pointer_to_member.cc b/test_cases/info_tests/member/pointer_to_member.cc index 5e5321c..20f9dcb 100644 --- a/test_cases/info_tests/member/pointer_to_member.cc +++ b/test_cases/info_tests/member/pointer_to_member.cc @@ -21,6 +21,9 @@ int (S::*s7)(); int (S::*s8)[7]; // declare s9 as volatile pointer to member of class S const int const int S::* volatile s9; +// declare s10 as function (pointer to member of class S int) returning int +int s10(int S::*); +int s10(int S::*) { return 0; } struct X { void f(int); diff --git a/test_cases/info_tests/source_filter/expected/type_roots_cc.elf_stg b/test_cases/info_tests/source_filter/expected/type_roots_cc.elf_stg new file mode 100644 index 0000000..e91b30c --- /dev/null +++ b/test_cases/info_tests/source_filter/expected/type_roots_cc.elf_stg @@ -0,0 +1,5 @@ +version: 0x00000002 +root_id: 0x84ea5130 +interface { + id: 0x84ea5130 +} diff --git a/test_cases/info_tests/source_filter/expected/composite_and_enum_cc.elf_stg b/test_cases/info_tests/source_filter/expected/types_cc.elf_stg index 4ec229b..f6c669d 100644 --- a/test_cases/info_tests/source_filter/expected/composite_and_enum_cc.elf_stg +++ b/test_cases/info_tests/source_filter/expected/types_cc.elf_stg @@ -1,5 +1,19 @@ version: 0x00000002 root_id: 0x84ea5130 +special { + id: 0x48b5725f + kind: VOID +} +pointer_reference { + id: 0x18bd6530 + kind: POINTER + pointee_type_id: 0x48b5725f +} +typedef { + id: 0x4f137329 + name: "Typedef" + referred_type_id: 0x18bd6530 +} struct_union { id: 0x54a61673 kind: STRUCT @@ -56,6 +70,14 @@ elf_symbol { full_name: "foo_struct" } elf_symbol { + id: 0x90772e58 + name: "foo_typedef" + is_defined: true + symbol_type: OBJECT + type_id: 0x4f137329 + full_name: "foo_typedef" +} +elf_symbol { id: 0xc77e6f56 name: "foo_union" is_defined: true @@ -69,5 +91,6 @@ interface { symbol_id: 0x5f43725e symbol_id: 0x49093003 symbol_id: 0x8361f42b + symbol_id: 0x90772e58 symbol_id: 0xc77e6f56 } diff --git a/test_cases/info_tests/source_filter/composite_and_enum.cc b/test_cases/info_tests/source_filter/type_roots.cc index b73bcb0..5d393dd 100644 --- a/test_cases/info_tests/source_filter/composite_and_enum.cc +++ b/test_cases/info_tests/source_filter/type_roots.cc @@ -20,8 +20,11 @@ enum class EnumClass { ONE = 1, }; +typedef void* Typedef; + Struct foo_struct; Union foo_union; Class foo_class; Enum foo_enum; EnumClass foo_enum_class; +Typedef foo_typedef; diff --git a/test_cases/info_tests/source_filter/types.cc b/test_cases/info_tests/source_filter/types.cc new file mode 100644 index 0000000..5d393dd --- /dev/null +++ b/test_cases/info_tests/source_filter/types.cc @@ -0,0 +1,30 @@ +struct Struct { + long x; +}; + +union Union { + long y; +}; + +class Class { + long z; +}; + +enum Enum { + ENUM_ZERO = 0, + ENUM_ONE = 1, +}; + +enum class EnumClass { + ZERO = 0, + ONE = 1, +}; + +typedef void* Typedef; + +Struct foo_struct; +Union foo_union; +Class foo_class; +Enum foo_enum; +EnumClass foo_enum_class; +Typedef foo_typedef; diff --git a/test_cases/info_tests/symbol/expected/tls_emulated_c.elf_stg b/test_cases/info_tests/symbol/expected/tls_emulated_c.elf_stg new file mode 100644 index 0000000..b4cd4f9 --- /dev/null +++ b/test_cases/info_tests/symbol/expected/tls_emulated_c.elf_stg @@ -0,0 +1,26 @@ +version: 0x00000002 +root_id: 0x84ea5130 +elf_symbol { + id: 0x688c97e0 + name: "__emutls_t.var2" + is_defined: true + symbol_type: OBJECT +} +elf_symbol { + id: 0xa2dcdaef + name: "__emutls_v.var1" + is_defined: true + symbol_type: OBJECT +} +elf_symbol { + id: 0xe19c7b87 + name: "__emutls_v.var2" + is_defined: true + symbol_type: OBJECT +} +interface { + id: 0x84ea5130 + symbol_id: 0x688c97e0 + symbol_id: 0xa2dcdaef + symbol_id: 0xe19c7b87 +} diff --git a/test_cases/info_tests/symbol/expected/tls_emulated_cc.elf_stg b/test_cases/info_tests/symbol/expected/tls_emulated_cc.elf_stg new file mode 100644 index 0000000..3ddf217 --- /dev/null +++ b/test_cases/info_tests/symbol/expected/tls_emulated_cc.elf_stg @@ -0,0 +1,38 @@ +version: 0x00000002 +root_id: 0x84ea5130 +primitive { + id: 0x6720d32f + name: "int" + encoding: SIGNED_INTEGER + bytesize: 0x00000004 +} +function { + id: 0x9d80e32f + return_type_id: 0x6720d32f +} +elf_symbol { + id: 0x92009dc1 + name: "_Z3barv" + is_defined: true + symbol_type: FUNCTION + type_id: 0x9d80e32f + full_name: "bar" +} +elf_symbol { + id: 0x6c57e66c + name: "__emutls_v._ZN2ns3fooE" + is_defined: true + symbol_type: OBJECT +} +elf_symbol { + id: 0xb0192ccb + name: "__emutls_v.foo" + is_defined: true + symbol_type: OBJECT +} +interface { + id: 0x84ea5130 + symbol_id: 0x92009dc1 + symbol_id: 0x6c57e66c + symbol_id: 0xb0192ccb +} diff --git a/test_cases/info_tests/symbol/expected/version_definition_c.elf_stg b/test_cases/info_tests/symbol/expected/version_definition_c.elf_stg index e9e0b9b..6342898 100644 --- a/test_cases/info_tests/symbol/expected/version_definition_c.elf_stg +++ b/test_cases/info_tests/symbol/expected/version_definition_c.elf_stg @@ -32,7 +32,7 @@ elf_symbol { is_defined: true symbol_type: FUNCTION type_id: 0x10985193 - full_name: "versioned_foo_v1" + full_name: "versioned_foo" } elf_symbol { id: 0xc828cd97 diff --git a/test_cases/info_tests/symbol/version_definition.c b/test_cases/info_tests/symbol/version_definition.c index 0127d57..af87429 100644 --- a/test_cases/info_tests/symbol/version_definition.c +++ b/test_cases/info_tests/symbol/version_definition.c @@ -6,14 +6,16 @@ void tweak(int dummy); +void versioned_foo(void) { tweak(1); } + __asm__(".symver versioned_foo_v1, versioned_foo@@VERS_1"); -void versioned_foo_v1(void) { tweak(1); } +void versioned_foo_v1(void) { tweak(2); } __asm__(".symver versioned_foo_v2, versioned_foo@VERS_2"); -void versioned_foo_v2(void) { tweak(2); } +void versioned_foo_v2(void) { tweak(3); } __asm__(".symver versioned_foo_v3, versioned_foo@VERS_3"); -void versioned_foo_v3(void) { tweak(3); } +void versioned_foo_v3(void) { tweak(4); } // Using a libc function helps to add the "version needs" section // in addition to the "version definitions". This helps to catch |