https://bugs.documentfoundation.org/show_bug.cgi?id=103690

--- Comment #8 from [email protected] ---
My internet is too slow to download the gigantic LO repo so I loaded up LO in a
debugger and disassembled Application::GetSolarMutex(), the function in which
the sefault (NULL pointer) dereference happened:

(lldb) disas
libvcllo.dylib`Application::GetSolarMutex:
->  0x102a823c0 <+0>:  pushq  %rbp
    0x102a823c1 <+1>:  movq   %rsp, %rbp
    0x102a823c4 <+4>:  callq  0x102a85050               ; ImplGetSVData()
    0x102a823c9 <+9>:  movq   0x8(%rax), %rdi
    0x102a823cd <+13>: movq   (%rdi), %rax              ; THIS IS THE OFFENDING
LINE
    0x102a823d0 <+16>: popq   %rbp
    0x102a823d1 <+17>: jmpq   *0xa8(%rax)
    0x102a823d7 <+23>: nopw   (%rax,%rax)

So, we expect to see a NULL pointer in %rdi, because it's trying to dereference
that and placing it into %rax. Let's check the register state.

Thread 0 crashed with X86 Thread State (64-bit):
  rax: 0x000000010b3989d0  rbx: 0x000060800005ec00  rcx: 0x0000608000115c30 
rdx: 0x0000600000248640
  rdi: 0x0000000000000000  rsi: 0x000000010b2a7d70  rbp: 0x00007fff5751a980 
rsp: 0x00007fff5751a980
   r8: 0x0000000000000000   r9: 0x0000000000000000  r10: 0x001faa01001faa80 
r11: 0x000000010b1f2f50
  r12: 0x0000000000000000  r13: 0x0000608000071380  r14: 0x0000600000248640 
r15: 0x0000000000000000
  rip: 0x000000010b1203cd  rfl: 0x0000000000010202  cr2: 0x0000000000000000

Indeed. %rdi is 0. Normally in such a short function I'd assume %rdi is the
first argument, as detailed in the calling convention
(https://en.wikipedia.org/wiki/X86_calling_conventions#System_V_AMD64_ABI).
However, we can see that %rdi is actually *(%rax + 8). And %rax is the return
value of the call to ImplGetSVData(). We know from the thread state that %rax
contains 0x000000010b3989d0, which isn't very useful right now since we don't
have a core dump. How is ImplGetSVData() implemented?

EDIT: Got the sources.

comphelper::SolarMutex& Application::GetSolarMutex()
{
    ImplSVData* pSVData = ImplGetSVData();
    return *(pSVData->mpDefInst->GetYieldMutex());
}

So mpDefInst is somehow NULL. Why?

(lldb) disas -n ImplGetSVData
libvcllo.dylib`ImplGetSVData:
    0x102a85050 <+0>:   pushq  %rbp
    0x102a85051 <+1>:   movq   %rsp, %rbp
    0x102a85054 <+4>:   pushq  %r14
    0x102a85056 <+6>:   pushq  %rbx
    0x102a85057 <+7>:   movq   0x27596a(%rip), %rbx      ; (anonymous
namespace)::rtl_Instance<ImplSVData, rtl::Static<ImplSVData, (anonymous
namespace)::private_aImplSVData>::StaticInstance, osl::Guard<osl::Mutex>,
osl::GetGlobalMutex, int, int>::m_pInstance
    0x102a8505e <+14>:  testq  %rbx, %rbx
    0x102a85061 <+17>:  jne    0x102a850f0               ; <+160>
    0x102a85067 <+23>:  callq  0x102b93dfa               ; symbol stub for:
osl_getGlobalMutex
    0x102a8506c <+28>:  movq   %rax, %r14
    0x102a8506f <+31>:  movq   (%r14), %rdi
    0x102a85072 <+34>:  callq  0x102b93d58               ; symbol stub for:
osl_acquireMutex
    0x102a85077 <+39>:  movq   0x27594a(%rip), %rbx      ; (anonymous
namespace)::rtl_Instance<ImplSVData, rtl::Static<ImplSVData, (anonymous
namespace)::private_aImplSVData>::StaticInstance, osl::Guard<osl::Mutex>,
osl::GetGlobalMutex, int, int>::m_pInstance
    0x102a8507e <+46>:  testq  %rbx, %rbx
    0x102a85081 <+49>:  jne    0x102a850e8               ; <+152>
    0x102a85083 <+51>:  movb   0x275ca7(%rip), %al       ; guard variable for
rtl::Static<ImplSVData, (anonymous
namespace)::private_aImplSVData>::StaticInstance::operator()()::instance
    0x102a85089 <+57>:  testb  %al, %al
    0x102a8508b <+59>:  jne    0x102a850da               ; <+138>
    0x102a8508d <+61>:  leaq   0x27593c(%rip), %rbx      ;
rtl::Static<ImplSVData, (anonymous
namespace)::private_aImplSVData>::StaticInstance::operator()()::instance
    0x102a85094 <+68>:  movl   $0x360, %esi              ; imm = 0x360
    0x102a85099 <+73>:  movq   %rbx, %rdi
    0x102a8509c <+76>:  callq  0x102b948f8               ; symbol stub for:
__bzero
    0x102a850a1 <+81>:  movw   $0x100, 0x275bee(%rip)    ;
rtl::Static<ImplSVData, (anonymous
namespace)::private_aImplSVData>::StaticInstance::operator()()::instance + 708,
imm = 0x100
    0x102a850aa <+90>:  movl   $0xffffffff, 0x275c0c(%rip) ;
rtl::Static<ImplSVData, (anonymous
namespace)::private_aImplSVData>::StaticInstance::operator()()::instance + 748,
imm = 0xFFFFFFFF
    0x102a850b4 <+100>: movw   $0x4000, 0x275c07(%rip)   ;
rtl::Static<ImplSVData, (anonymous
namespace)::private_aImplSVData>::StaticInstance::operator()()::instance + 752,
imm = 0x4000
    0x102a850bd <+109>: leaq   0xeec(%rip), %rdi         ;
ImplSVData::~ImplSVData()
    0x102a850c4 <+116>: leaq   -0x2ce0cb(%rip), %rdx
    0x102a850cb <+123>: movq   %rbx, %rsi
    0x102a850ce <+126>: callq  0x102b948fe               ; symbol stub for:
__cxa_atexit
    0x102a850d3 <+131>: movb   $0x1, 0x275c56(%rip)      ;
rtl::Static<ImplSVData, (anonymous
namespace)::private_aImplSVData>::StaticInstance::operator()()::instance + 863
    0x102a850da <+138>: leaq   0x2758ef(%rip), %rbx      ;
rtl::Static<ImplSVData, (anonymous
namespace)::private_aImplSVData>::StaticInstance::operator()()::instance
    0x102a850e1 <+145>: movq   %rbx, 0x2758e0(%rip)      ; (anonymous
namespace)::rtl_Instance<ImplSVData, rtl::Static<ImplSVData, (anonymous
namespace)::private_aImplSVData>::StaticInstance, osl::Guard<osl::Mutex>,
osl::GetGlobalMutex, int, int>::m_pInstance
    0x102a850e8 <+152>: movq   (%r14), %rdi
    0x102a850eb <+155>: callq  0x102b93e66               ; symbol stub for:
osl_releaseMutex
    0x102a850f0 <+160>: movq   %rbx, %rax
    0x102a850f3 <+163>: popq   %rbx
    0x102a850f4 <+164>: popq   %r14
    0x102a850f6 <+166>: popq   %rbp
    0x102a850f7 <+167>: retq
    0x102a850f8 <+168>: movq   %rax, %rdi
    0x102a850fb <+171>: callq  0x1027bc6f0               ;
__clang_call_terminate

Well, that's annoying... The code?

ImplSVData* ImplGetSVData() {
    return &private_aImplSVData::get();
}

Heavily inlined. And private_aImplSVData is?

namespace
{
    struct private_aImplSVData :
        public rtl::Static<ImplSVData, private_aImplSVData> {};
}

What the...? I have no idea what this is. A class inheriting from a class that
takes two template parameters, including itself (Curiously Recurring Template
Pattern?). Sure. The strange thing is that private_aImplSVData is mentioned
only on the 3 lines I've already printed out. It's empty. I suppose ImplSVData
is the true meat here, then. It's defined as:

struct ImplSVData
{
    SalData*                mpSalData = nullptr;
    SalInstance*            mpDefInst = nullptr;            // Default
SalInstance
    Application*            mpApp = nullptr;                // pApp
    VclPtr<WorkWindow>      mpDefaultWin;                   // Default-Window
    bool                    mbDeInit = false;               // Is VCL
deinitializing
    // Tons of stuff more.
}

Right, so the second pointer plainly never gets initialized.

Anyway, back to this rtl::Static thingy, which I assume is some sort of fancy
wrapper to make a singleton out of everything, the Java programmers' dream.
It's a doozy, we find it in core/include/rtl/instance.hxx:

/** Helper base class for a late-initialized (default-constructed)
    static variable, implementing the double-checked locking pattern correctly.

    @derive
    Derive from this class (common practice), e.g.
    <pre>
    struct MyStatic : public rtl::Static<MyType, MyStatic> {};
    ...
    MyType & rStatic = MyStatic::get();
    ...
    </pre>

    @tparam T
              variable's type
    @tparam Unique
              Implementation trick to make the inner static holder unique,
              using the outer class
              (the one that derives from this base class)
*/
#if HAVE_THREADSAFE_STATICS
template<typename T, typename Unique>
class Static {
public:
    /** Gets the static.  Mutual exclusion is implied by a functional
        -fthreadsafe-statics

        @return
                static variable
    */
    static T & get() {
        static T instance;
        return instance;
    }
};
#else
template<typename T, typename Unique>
class Static {
public:
    /** Gets the static.  Mutual exclusion is performed using the
        osl global mutex.

        @return
                static variable
    */
    static T & get() {
        return *rtl_Instance<
            T, StaticInstance,
            ::osl::MutexGuard, ::osl::GetGlobalMutex >::create(
                StaticInstance(), ::osl::GetGlobalMutex() );
    }
private:
    struct StaticInstance {
        T * operator () () {
            static T instance;
            return &instance;
        }
    };
};
#endif

One very interesting thing to note here is that the disassembly of
ImplGetSVData I posted earlier contained calls to ::osl::GetGlobalMutex. Which
means that for some reason on macOS Sierra, something thinks we don't
HAVE_THREADSAFE_STATICS. I'm pretty sure we do on macOS Sierra, with any recent
GCC or Clang for sure, and I bet this one was built with xcode (thus Clang).

This (http://stackoverflow.com/a/35080639) SO answer implies that
-f(no-)threadsafe-statics flag is GCC specific. Indeed I can't find it in my
clang(1) man page. So that might be another "bug", basically assuming that the
compiler is GCC.

I won't even paste the code of rtl_Instance here (it's in the same file), but
suffice to say that a lot of text is spent on indicating how/why it can be
optimized on which compilers. Just avoiding this shenanigans would thus be a
good optimization, it seems.

Anyhow. It's pretty clear that our ImplSVData is just not being initialized,
and it's being called for the first time by GetSolarMutex, which obviously
didn't expect an uninitialized ImplSVData back.

The LO codebase is not familiar to me, so I'll leave it to someone with
experience to figure out why ImplSVData is not being initialized.

Thanks

-- 
You are receiving this mail because:
You are the assignee for the bug.
_______________________________________________
Libreoffice-bugs mailing list
[email protected]
https://lists.freedesktop.org/mailman/listinfo/libreoffice-bugs

Reply via email to