diff --git a/libunwind/src/DwarfInstructions.hpp b/libunwind/src/DwarfInstructions.hpp --- a/libunwind/src/DwarfInstructions.hpp +++ b/libunwind/src/DwarfInstructions.hpp @@ -232,6 +232,31 @@ } #endif +#if defined(_LIBUNWIND_TARGET_PPC64) +#define PPC64_ELFV1_R2_LOAD_INST_ENCODING 0xe8410028u // ld r2,40(r1) +#define PPC64_ELFV1_R2_OFFSET 40 +#define PPC64_ELFV2_R2_LOAD_INST_ENCODING 0xe8410018u // ld r2,24(r1) +#define PPC64_ELFV2_R2_OFFSET 24 + // If the instruction at return address is a TOC (r2) restore, + // then r2 was saved and needs to be restored. + // ELFv2 ABI specifies that the TOC Pointer must be saved at SP + 24, + // while in ELFv1 ABI it is saved at SP + 40. + if (R::getArch() == REGISTERS_PPC64 && returnAddress != 0) { + pint_t sp = newRegisters.getRegister(UNW_REG_SP); + pint_t r2 = 0; + switch (addressSpace.get32(returnAddress)) { + case PPC64_ELFV1_R2_LOAD_INST_ENCODING: + r2 = addressSpace.get64(sp + PPC64_ELFV1_R2_OFFSET); + break; + case PPC64_ELFV2_R2_LOAD_INST_ENCODING: + r2 = addressSpace.get64(sp + PPC64_ELFV2_R2_OFFSET); + break; + } + if (r2) + newRegisters.setRegister(UNW_PPC64_R2, r2); + } +#endif + // Return address is address after call site instruction, so setting IP to // that does simualates a return. newRegisters.setIP(returnAddress); diff --git a/libunwind/src/assembly.h b/libunwind/src/assembly.h --- a/libunwind/src/assembly.h +++ b/libunwind/src/assembly.h @@ -36,6 +36,20 @@ #define SEPARATOR ; #endif +#if defined(__powerpc64__) && (!defined(_CALL_ELF) || _CALL_ELF == 1) +#define PPC64_OPD1 .section .opd,"aw",@progbits SEPARATOR +#define PPC64_OPD2 SEPARATOR \ + .p2align 3 SEPARATOR \ + .quad .Lfunc_begin0 SEPARATOR \ + .quad .TOC.@tocbase SEPARATOR \ + .quad 0 SEPARATOR \ + .text SEPARATOR \ +.Lfunc_begin0: +#else +#define PPC64_OPD1 +#define PPC64_OPD2 +#endif + #define GLUE2(a, b) a ## b #define GLUE(a, b) GLUE2(a, b) #define SYMBOL_NAME(name) GLUE(__USER_LABEL_PREFIX__, name) @@ -123,7 +137,9 @@ .globl SYMBOL_NAME(name) SEPARATOR \ HIDDEN_SYMBOL(SYMBOL_NAME(name)) SEPARATOR \ SYMBOL_IS_FUNC(SYMBOL_NAME(name)) SEPARATOR \ - SYMBOL_NAME(name): + PPC64_OPD1 \ + SYMBOL_NAME(name): \ + PPC64_OPD2 #if defined(__arm__) #if !defined(__ARM_ARCH) diff --git a/libunwind/test/exception.pass.cpp b/libunwind/test/exception.pass.cpp new file mode 100644 --- /dev/null +++ b/libunwind/test/exception.pass.cpp @@ -0,0 +1,74 @@ +#include +#include +#include + +// NOTE: PowerPC64 had an issue with accessing +// global data after catching an exception +static int myGlobal1 = 111; +static int myGlobal2 = 222; +static int myGlobal3 = 333; + +// Test exception handled at same level +static int test1() { + try { + throw std::exception(); + } catch (const std::exception &e) { + return myGlobal1; + } + return 0; +} + +static int level3() { + try { + throw std::runtime_error("ERROR"); + } catch (const std::invalid_argument &e) { + return -3; + } + return 3; +} + +static int level2() { + try { + return level3(); + } catch (const std::out_of_range &e) { + return -2; + } + return 2; +} + +// Test exception handled at a lower level +static int test2() { + try { + return level2(); + } catch (const std::exception &e) { + return myGlobal2; + } + return 1; +} + +void raiseException() { + if (myGlobal3 != 0) + throw std::exception(); +} + +// Test handling of an exception thrown from a function that +// uses a different PPC64 TOC. +static int test3() { + void (*f)(); + + try { + f = &raiseException; + // Force call through Global Entry Point (on PPC64) + (*f)(); + } catch (const std::exception &e) { + return myGlobal3; + } + return 0; +} + +int main() { + assert(test1() == myGlobal1); + assert(test2() == myGlobal2); + assert(test3() == myGlobal3); + return 0; +} diff --git a/libunwind/test/lit.cfg b/libunwind/test/lit.cfg --- a/libunwind/test/lit.cfg +++ b/libunwind/test/lit.cfg @@ -23,6 +23,9 @@ # test_source_root: The root path where tests are located. config.test_source_root = os.path.dirname(__file__) +# needed to test libunwind with code that throws exceptions +config.enable_exceptions = True + # Infer the libcxx_test_source_root for configuration import. # If libcxx_source_root isn't specified in the config, assume that the libcxx # and libunwind source directories are sibling directories.