From 3256aa8fe6fd785df12cc39f95e55bef0bc371c2 Mon Sep 17 00:00:00 2001 From: Raphael Isemann Date: Mon, 11 Oct 2021 13:35:15 +0200 Subject: [PATCH] [lldb] Add support for DW_AT_calling_convention to the DWARF parser This adds support for parsing DW_AT_calling_convention in the DWARF parser. The generic DWARF parsing code already support extracting this attribute from A DIE and TypeSystemClang already offers a parameter to add a calling convention to a function type (as the PDB parser supports calling convention parsing), so this patch just converts the DWARF enum value to the Clang enum value and adds a few tests. There are two tests in this patch.: * A unit test for the added DWARF parsing code that should run on all platforms. * An API tests that covers the whole expression evaluation machinery by trying to call functions with non-standard calling conventions. The specific subtests are target specific as some calling conventions only work on e.g. win32 (or, if they work on other platforms they only really have observable differences on a specific target). The tests are also highly compiler-specific, so if GCC or Clang tell us that they don't support a specific calling convention then we just skip the test. Note that some calling conventions are supported by Clang but aren't implemented in LLVM (e.g. `pascal`), so there we just test that if this ever gets implemented in LLVM that LLDB works too. There are also some more tricky/obscure conventions that are left out such as the different swift* conventions, some planned Obj-C conventions (`Preserve*`), AAPCS* conventions (as the DWARF->Clang conversion is ambiguous for AAPCS and APPCS-VFP) and conventions only used for OpenCL etc. Reviewed By: aprantl Differential Revision: https://reviews.llvm.org/D108629 --- .../SymbolFile/DWARF/DWARFASTParserClang.cpp | 36 +++- .../API/lang/c/calling-conventions/Makefile | 1 + .../TestCCallingConventions.py | 78 +++++++++ .../API/lang/c/calling-conventions/fastcall.c | 7 + .../API/lang/c/calling-conventions/ms_abi.c | 7 + .../API/lang/c/calling-conventions/pascal.c | 7 + .../API/lang/c/calling-conventions/regcall.c | 7 + .../API/lang/c/calling-conventions/stdcall.c | 7 + .../API/lang/c/calling-conventions/sysv_abi.c | 7 + .../lang/c/calling-conventions/vectorcall.c | 7 + .../DWARF/DWARFASTParserClangTests.cpp | 156 ++++++++++++++++++ 11 files changed, 319 insertions(+), 1 deletion(-) create mode 100644 lldb/test/API/lang/c/calling-conventions/Makefile create mode 100644 lldb/test/API/lang/c/calling-conventions/TestCCallingConventions.py create mode 100644 lldb/test/API/lang/c/calling-conventions/fastcall.c create mode 100644 lldb/test/API/lang/c/calling-conventions/ms_abi.c create mode 100644 lldb/test/API/lang/c/calling-conventions/pascal.c create mode 100644 lldb/test/API/lang/c/calling-conventions/regcall.c create mode 100644 lldb/test/API/lang/c/calling-conventions/stdcall.c create mode 100644 lldb/test/API/lang/c/calling-conventions/sysv_abi.c create mode 100644 lldb/test/API/lang/c/calling-conventions/vectorcall.c diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp index 1aa2c4f3b1ea..c29b42dec08b 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp @@ -876,6 +876,37 @@ TypeSP DWARFASTParserClang::ParseEnum(const SymbolContext &sc, return type_sp; } +static clang::CallingConv +ConvertDWARFCallingConventionToClang(const ParsedDWARFTypeAttributes &attrs) { + switch (attrs.calling_convention) { + case llvm::dwarf::DW_CC_normal: + return clang::CC_C; + case llvm::dwarf::DW_CC_BORLAND_stdcall: + return clang::CC_X86StdCall; + case llvm::dwarf::DW_CC_BORLAND_msfastcall: + return clang::CC_X86FastCall; + case llvm::dwarf::DW_CC_LLVM_vectorcall: + return clang::CC_X86VectorCall; + case llvm::dwarf::DW_CC_BORLAND_pascal: + return clang::CC_X86Pascal; + case llvm::dwarf::DW_CC_LLVM_Win64: + return clang::CC_Win64; + case llvm::dwarf::DW_CC_LLVM_X86_64SysV: + return clang::CC_X86_64SysV; + case llvm::dwarf::DW_CC_LLVM_X86RegCall: + return clang::CC_X86RegCall; + default: + break; + } + + Log *log(LogChannelDWARF::GetLogIfAny(DWARF_LOG_TYPE_COMPLETION | + DWARF_LOG_LOOKUPS)); + LLDB_LOG(log, "Unsupported DW_AT_calling_convention value: {0}", + attrs.calling_convention); + // Use the default calling convention as a fallback. + return clang::CC_C; +} + TypeSP DWARFASTParserClang::ParseSubroutine(const DWARFDIE &die, ParsedDWARFTypeAttributes &attrs) { Log *log(LogChannelDWARF::GetLogIfAny(DWARF_LOG_TYPE_COMPLETION | @@ -954,11 +985,14 @@ TypeSP DWARFASTParserClang::ParseSubroutine(const DWARFDIE &die, is_cxx_method = false; } + clang::CallingConv calling_convention = + ConvertDWARFCallingConventionToClang(attrs); + // clang_type will get the function prototype clang type after this // call CompilerType clang_type = m_ast.CreateFunctionType( return_clang_type, function_param_types.data(), - function_param_types.size(), is_variadic, type_quals); + function_param_types.size(), is_variadic, type_quals, calling_convention); if (attrs.name) { bool type_handled = false; diff --git a/lldb/test/API/lang/c/calling-conventions/Makefile b/lldb/test/API/lang/c/calling-conventions/Makefile new file mode 100644 index 000000000000..22f1051530f8 --- /dev/null +++ b/lldb/test/API/lang/c/calling-conventions/Makefile @@ -0,0 +1 @@ +include Makefile.rules diff --git a/lldb/test/API/lang/c/calling-conventions/TestCCallingConventions.py b/lldb/test/API/lang/c/calling-conventions/TestCCallingConventions.py new file mode 100644 index 000000000000..5156190bc071 --- /dev/null +++ b/lldb/test/API/lang/c/calling-conventions/TestCCallingConventions.py @@ -0,0 +1,78 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +from lldbsuite.test_event.build_exception import BuildError + +class TestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + NO_DEBUG_INFO_TESTCASE = True + + def build_and_run(self, test_file): + """ + Tries building the given test source and runs to the first breakpoint. + Returns false if the file fails to build due to an unsupported calling + convention on the current test target. Returns true if building and + running to the breakpoint succeeded. + """ + try: + self.build(dictionary={ + "C_SOURCES" : test_file, + "CFLAGS_EXTRAS" : "-Werror" + }) + except BuildError as e: + # Test source failed to build. Check if it failed because the + # calling convention argument is unsupported/unknown in which case + # the test should be skipped. + error_msg = str(e) + # Clang gives an explicit error when a calling convention is + # not supported. + if "calling convention is not supported for this target" in error_msg: + return False + # GCC's has two different generic warnings it can emit. + if "attribute ignored" in error_msg: + return False + if "attribute directive ignored " in error_msg: + return False + # We got a different build error, so raise it again to fail the + # test. + raise + lldbutil.run_to_source_breakpoint(self, "// break here", lldb.SBFileSpec(test_file)) + return True + + def test_regcall(self): + if not self.build_and_run("regcall.c"): + return + self.expect_expr("func(1, 2, 3, 4)", result_type="int", result_value="10") + + def test_ms_abi(self): + if not self.build_and_run("ms_abi.c"): + return + self.expect_expr("func(1, 2, 3, 4)", result_type="int", result_value="10") + + def test_stdcall(self): + if not self.build_and_run("stdcall.c"): + return + self.expect_expr("func(1, 2, 3, 4)", result_type="int", result_value="10") + + def test_vectorcall(self): + if not self.build_and_run("vectorcall.c"): + return + self.expect_expr("func(1.0)", result_type="int", result_value="1") + + def test_fastcall(self): + if not self.build_and_run("fastcall.c"): + return + self.expect_expr("func(1, 2, 3, 4)", result_type="int", result_value="10") + + def test_pascal(self): + if not self.build_and_run("pascal.c"): + return + self.expect_expr("func(1, 2, 3, 4)", result_type="int", result_value="10") + + def test_sysv_abi(self): + if not self.build_and_run("sysv_abi.c"): + return + self.expect_expr("func(1, 2, 3, 4)", result_type="int", result_value="10") diff --git a/lldb/test/API/lang/c/calling-conventions/fastcall.c b/lldb/test/API/lang/c/calling-conventions/fastcall.c new file mode 100644 index 000000000000..2a6937d7a9cc --- /dev/null +++ b/lldb/test/API/lang/c/calling-conventions/fastcall.c @@ -0,0 +1,7 @@ +int __attribute__((fastcall)) func(int a, int b, int c, int d) { + return a + b + c + d; +} + +int main() { + return func(1, 2, 3, 4); // break here +} diff --git a/lldb/test/API/lang/c/calling-conventions/ms_abi.c b/lldb/test/API/lang/c/calling-conventions/ms_abi.c new file mode 100644 index 000000000000..6b40cbc5d3fa --- /dev/null +++ b/lldb/test/API/lang/c/calling-conventions/ms_abi.c @@ -0,0 +1,7 @@ +int __attribute__((ms_abi)) func(int a, int b, int c, int d) { + return a + b + c + d; +} + +int main() { + return func(1, 2, 3, 4); // break here +} diff --git a/lldb/test/API/lang/c/calling-conventions/pascal.c b/lldb/test/API/lang/c/calling-conventions/pascal.c new file mode 100644 index 000000000000..ed0e3b3735fa --- /dev/null +++ b/lldb/test/API/lang/c/calling-conventions/pascal.c @@ -0,0 +1,7 @@ +int __attribute__((pascal)) func(int a, int b, int c, int d) { + return a + b + c + d; +} + +int main() { + return func(1, 2, 3, 4); // break here +} diff --git a/lldb/test/API/lang/c/calling-conventions/regcall.c b/lldb/test/API/lang/c/calling-conventions/regcall.c new file mode 100644 index 000000000000..6a683a9b060a --- /dev/null +++ b/lldb/test/API/lang/c/calling-conventions/regcall.c @@ -0,0 +1,7 @@ +int __attribute__((regcall)) func(int a, int b, int c, int d) { + return a + b + c + d; +} + +int main() { + return func(1, 2, 3, 4); // break here +} diff --git a/lldb/test/API/lang/c/calling-conventions/stdcall.c b/lldb/test/API/lang/c/calling-conventions/stdcall.c new file mode 100644 index 000000000000..82df064b1bd8 --- /dev/null +++ b/lldb/test/API/lang/c/calling-conventions/stdcall.c @@ -0,0 +1,7 @@ +int __attribute__((stdcall)) func(int a, int b, int c, int d) { + return a + b + c + d; +} + +int main() { + return func(1, 2, 3, 4); // break here +} diff --git a/lldb/test/API/lang/c/calling-conventions/sysv_abi.c b/lldb/test/API/lang/c/calling-conventions/sysv_abi.c new file mode 100644 index 000000000000..309b2624d0b6 --- /dev/null +++ b/lldb/test/API/lang/c/calling-conventions/sysv_abi.c @@ -0,0 +1,7 @@ +int __attribute__((sysv_abi)) func(int a, int b, int c, int d) { + return a + b + c + d; +} + +int main() { + return func(1, 2, 3, 4); // break here +} diff --git a/lldb/test/API/lang/c/calling-conventions/vectorcall.c b/lldb/test/API/lang/c/calling-conventions/vectorcall.c new file mode 100644 index 000000000000..5e0e1e599b6a --- /dev/null +++ b/lldb/test/API/lang/c/calling-conventions/vectorcall.c @@ -0,0 +1,7 @@ +int __attribute__((vectorcall)) func(double a) { + return (int)a; +} + +int main() { + return func(1.0); // break here +} diff --git a/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp b/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp index 435da1f9643b..a44c88a3d3b1 100644 --- a/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp +++ b/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp @@ -116,3 +116,159 @@ DWARF: testing::UnorderedElementsAre(decl_ctxs[0], decl_ctxs[3])); } +TEST_F(DWARFASTParserClangTests, TestCallingConventionParsing) { + // Tests parsing DW_AT_calling_convention values. + + // The DWARF below just declares a list of function types with + // DW_AT_calling_convention on them. + const char *yamldata = R"( +--- !ELF +FileHeader: + Class: ELFCLASS32 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_386 +DWARF: + debug_str: + - func1 + - func2 + - func3 + - func4 + - func5 + - func6 + - func7 + - func8 + - func9 + debug_abbrev: + - ID: 0 + Table: + - Code: 0x1 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_language + Form: DW_FORM_data2 + - Code: 0x2 + Tag: DW_TAG_subprogram + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_low_pc + Form: DW_FORM_addr + - Attribute: DW_AT_high_pc + Form: DW_FORM_data4 + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Attribute: DW_AT_calling_convention + Form: DW_FORM_data1 + - Attribute: DW_AT_external + Form: DW_FORM_flag_present + debug_info: + - Version: 4 + AddrSize: 4 + Entries: + - AbbrCode: 0x1 + Values: + - Value: 0xC + - AbbrCode: 0x2 + Values: + - Value: 0x0 + - Value: 0x5 + - Value: 0x00 + - Value: 0xCB + - Value: 0x1 + - AbbrCode: 0x2 + Values: + - Value: 0x10 + - Value: 0x5 + - Value: 0x06 + - Value: 0xB3 + - Value: 0x1 + - AbbrCode: 0x2 + Values: + - Value: 0x20 + - Value: 0x5 + - Value: 0x0C + - Value: 0xB1 + - Value: 0x1 + - AbbrCode: 0x2 + Values: + - Value: 0x30 + - Value: 0x5 + - Value: 0x12 + - Value: 0xC0 + - Value: 0x1 + - AbbrCode: 0x2 + Values: + - Value: 0x40 + - Value: 0x5 + - Value: 0x18 + - Value: 0xB2 + - Value: 0x1 + - AbbrCode: 0x2 + Values: + - Value: 0x50 + - Value: 0x5 + - Value: 0x1E + - Value: 0xC1 + - Value: 0x1 + - AbbrCode: 0x2 + Values: + - Value: 0x60 + - Value: 0x5 + - Value: 0x24 + - Value: 0xC2 + - Value: 0x1 + - AbbrCode: 0x2 + Values: + - Value: 0x70 + - Value: 0x5 + - Value: 0x2a + - Value: 0xEE + - Value: 0x1 + - AbbrCode: 0x2 + Values: + - Value: 0x80 + - Value: 0x5 + - Value: 0x30 + - Value: 0x01 + - Value: 0x1 + - AbbrCode: 0x0 +... +)"; + YAMLModuleTester t(yamldata); + + DWARFUnit *unit = t.GetDwarfUnit(); + ASSERT_NE(unit, nullptr); + const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); + ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); + DWARFDIE cu_die(unit, cu_entry); + + TypeSystemClang ast_ctx("dummy ASTContext", HostInfoBase::GetTargetTriple()); + DWARFASTParserClangStub ast_parser(ast_ctx); + + std::vector found_function_types; + // The DWARF above is just a list of functions. Parse all of them to + // extract the function types and their calling convention values. + for (DWARFDIE func : cu_die.children()) { + ASSERT_EQ(func.Tag(), DW_TAG_subprogram); + SymbolContext sc; + bool new_type = false; + lldb::TypeSP type = ast_parser.ParseTypeFromDWARF(sc, func, &new_type); + found_function_types.push_back( + type->GetForwardCompilerType().GetTypeName().AsCString()); + } + + // Compare the parsed function types against the expected list of types. + const std::vector expected_function_types = { + "void () __attribute__((regcall))", + "void () __attribute__((fastcall))", + "void () __attribute__((stdcall))", + "void () __attribute__((vectorcall))", + "void () __attribute__((pascal))", + "void () __attribute__((ms_abi))", + "void () __attribute__((sysv_abi))", + "void ()", // invalid calling convention. + "void ()", // DW_CC_normal -> no attribute + }; + ASSERT_EQ(found_function_types, expected_function_types); +}