KISS

Keep It Simple Stupid

Thread Sanitizer reports null symbols

| comments

Clang has this useful tool called Thread Sanitizer that can detect data races in a program and Xcode makes it easy to use it by providing the “Thread Sanitizer” checkbox in the scheme’s Diagnostics page for Run and Test actions. I needed to run an iOS program with the TSan to see if it would find anything. It did, but all the stracktraces showed only <null>s instead of all symbols, so they weren’t at all clear. I’ve found a workaround how to see that information using breakpoints.

Here’s what I see in the console output when an issue is found:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
​==================
WARNING: ThreadSanitizer: data race (pid=29534)
  Write of size 8 at 0x7b300005e618 by thread T7:
    #0 <null> <null> (MyLibrary:x86_64+0x2241dc)
    #1 <null> <null> (MyLibrary:x86_64+0x22ac6b)
    #2 <null> <null> (MyApp:x86_64+0xf9b25)
    #3 <null> <null> (MyApp:x86_64+0xf9d21)
    #4 <null> <null> (MyApp:x86_64+0xf9db3)
    #5 <null> <null> (libclang_rt.tsan_iossim_dynamic.dylib:x86_64+0x6f53b)
    #6 <null> <null> (libdispatch.dylib:x86_64+0x2e8d)

  Previous write of size 8 at 0x7b300005e618 by thread T1:
    #0 <null> <null> (MyLibrary:x86_64+0x2241dc)
    #1 <null> <null> (MyLibrary:x86_64+0x22ac6b)
    #2 <null> <null> (MyApp:x86_64+0xf9b25)
    #3 <null> <null> (MyApp:x86_64+0xf9d21)
    #4 <null> <null> (MyApp:x86_64+0xf9db3)
    #5 <null> <null> (libclang_rt.tsan_iossim_dynamic.dylib:x86_64+0x6f53b)
    #6 <null> <null> (libdispatch.dylib:x86_64+0x2e8d)

  Location is heap block of size 184 at 0x7b300005e5c0 allocated by main thread:
    #0 <null> <null> (libclang_rt.tsan_iossim_dynamic.dylib:x86_64+0x4dbea)
    #1 <null> <null> (libswiftCore.dylib:x86_64+0x2f8b88)
    #2 <null> <null> (MyLibrary:x86_64+0x231add)
    #3 <null> <null> (MyLibrary:x86_64+0x2310d8)
    #4 <null> <null> (MyLibrary:x86_64+0x20405b)
    #5 <null> <null> (MyLibrary:x86_64+0x201e90)
    #6 <null> <null> (MyLibrary:x86_64+0x20687b)
    #7 <null> <null> (MyLibrary:x86_64+0x4b0d03)
    #8 <null> <null> (MyLibrary:x86_64+0x163dd3)
    #9 <null> <null> (MyApp:x86_64+0x8f545)
    #10 <null> <null> (App:x86_64+0x10000ccfb)
    #11 <null> <null> (App:x86_64+0x100005d21)
    #12 <null> <null> (App:x86_64+0x1000063f8)
    #13 <null> <null> (UIKitCore:x86_64+0xade60f)
    #14 <null> <null> (libdyld.dylib:x86_64+0x11fc)

  Thread T7 (tid=2151334, running) is a GCD worker thread

  Thread T1 (tid=2151186, running) is a GCD worker thread

SUMMARY: ThreadSanitizer: data race (~/Library/Developer/Xcode/DerivedData/OneApp-foo/Build/Products/Debug-iphonesimulator/MyLibrary.framework/MyLibrary:x86_64+0x2241dc) 
​==================
ThreadSanitizer report breakpoint hit. Use 'thread info -s' to get extended information about the report.

Cleaning Derived Data and rebuilding didn’t help. I have no idea why it doesn’t display the debug symbols.

So I added a Runtime Issue Breakpoint, which did stop in my code when the TSan found a data race:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
* thread #9, queue = 'SpecialQueue', stop reason = Data race detected
    frame #0: 0x00000001023c2a20 libclang_rt.tsan_iossim_dynamic.dylib`__tsan_on_report
    frame #1: 0x00000001023c4b9f libclang_rt.tsan_iossim_dynamic.dylib`__tsan::OutputReport(__tsan::ThreadState*, __tsan::ScopedReport const&) + 511
    frame #2: 0x00000001023c5b51 libclang_rt.tsan_iossim_dynamic.dylib`__tsan::ReportRace(__tsan::ThreadState*) + 3425
  * frame #3: 0x000000010a4841dd MyLibrary`Foo.bar(self=0x00007b300005e5c0) at Foo.LegacyTileImageProvider.swift:173:36
    frame #4: 0x000000010f3a7d22 MyApp`partial apply for closure #2 in Baz.requestData() at <compiler-generated>:0
    frame #5: 0x000000010f3a7db4 MyApp`thunk for @escaping @callee_guaranteed () -> () at <compiler-generated>:0
    frame #6: 0x00000001023ce53c libclang_rt.tsan_iossim_dynamic.dylib`__tsan::invoke_and_release_block(void*) + 12
    frame #7: 0x00000001023ce292 libclang_rt.tsan_iossim_dynamic.dylib`__tsan::dispatch_callback_wrap(void*) + 306
    frame #8: 0x0000000111939e8e libdispatch.dylib`_dispatch_client_callout + 8
    frame #9: 0x000000011193c7a3 libdispatch.dylib`_dispatch_continuation_pop + 552
    frame #10: 0x000000011193bbbb libdispatch.dylib`_dispatch_async_redirect_invoke + 771
    frame #11: 0x000000011194b399 libdispatch.dylib`_dispatch_root_queue_drain + 351
    frame #12: 0x000000011194bca6 libdispatch.dylib`_dispatch_worker_thread2 + 135
    frame #13: 0x00007fff523019f7 libsystem_pthread.dylib`_pthread_wqthread + 220
    frame #14: 0x00007fff52300b77 libsystem_pthread.dylib`start_wqthread + 15

A runtime issue breakpoint is too broad because it also stops on other errors. What you need is to add a symbolic breakpoint for __tsan_on_report in Xcode, or in LLDB:

1
2
3
4
5
6
(lldb) breakpoint set -b __tsan_on_report
Breakpoint 10: where = libclang_rt.tsan_iossim_dynamic.dylib`__tsan_on_report, address = 0x00000001023c2a20
(lldb) b
Current breakpoints:
10: name = '__tsan_on_report', locations = 1, resolved = 1, hit count = 0
  10.1: where = libclang_rt.tsan_iossim_dynamic.dylib`__tsan_on_report, address = 0x00000001023c2a20, resolved, hit count = 0 

Now you’ll see those symbolicated stacktraces, at least for the thread where the data race is actually happening. I think you can enhance the breakpoint by automatically printing the thread’s backtrace and continuing the program if you only want to see what data races there are without inspecting each one in the debugger.

ps. I later found that Xcode also shows these runtime issues in the project’s Issue Navigator (Cmd+5), “Runtime” section.

Comments