diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp @@ -876,6 +876,37 @@ 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 @@ 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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- a/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp +++ b/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp @@ -116,3 +116,159 @@ 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); +}