From f724c08a879c791213c8c4333e1e9c6a06c1071d Mon Sep 17 00:00:00 2001 From: Lionello Lunesu Date: Sat, 1 Feb 2020 09:16:07 +0800 Subject: [PATCH] Fix issue 20019: Symbol not found: _dyld_enumerate_tlv_storage on macOS 10.15 Ported from 378fc6dfadc8b638616d146496eaee79995678ac The symbol `dyld_enumerate_tlv_storage` has been used to iterate all the thread local storage addresses and registered them as roots with the garbage collector. The symbol has always been a private symbol, not part of the public API. In the latest version of macOS, 10.15 it has been removed. This replaces the usage of `dyld_enumerate_tlv_storage` by manually iterating the TLV sections of all images and get key that the dynamic loader has setup for each TLS symbol. The key is then used to get the base address of the TLV storage. --- src/rt/sections_ldc.d | 224 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 210 insertions(+), 14 deletions(-) diff --git a/src/rt/sections_ldc.d b/src/rt/sections_ldc.d index 64acb8f576..b9d19fa430 100644 --- a/src/rt/sections_ldc.d +++ b/src/rt/sections_ldc.d @@ -424,15 +424,7 @@ void finiSections() private { - version (OSX) - { - extern(C) void _d_dyld_getTLSRange(void*, void**, size_t*); - private align(16) ubyte dummyTlsSymbol = 42; - // By initalizing dummyTlsSymbol with something non-zero and aligning - // to 16-bytes, section __thread_data will be aligned as a workaround - // for https://github.com/ldc-developers/ldc/issues/1252 - } - else version (Windows) + version (Windows) { extern(C) extern { @@ -450,11 +442,9 @@ void[] initTLSRanges() debug(PRINTF) printf("initTLSRanges called\n"); version (OSX) { - void* start = null; - size_t size = 0; - _d_dyld_getTLSRange(&dummyTlsSymbol, &start, &size); - assert(start && size, "Could not determine TLS range."); - return start[0 .. size]; + auto range = getTLSRange(); + assert(range.isValid, "Could not determine TLS range."); + return range.toArray; } else version (linux) { @@ -526,3 +516,209 @@ body return cast(immutable)result; } + +import core.sys.posix.pthread; +import core.stdc.config; +struct tlv_descriptor +{ + void* function (tlv_descriptor*) thunk; + c_ulong key; + c_ulong offset; +} +struct segment_command_64 +{ + uint cmd; + uint cmdsize; + char[16] segname = 0; + ulong vmaddr; + ulong vmsize; + ulong fileoff; + ulong filesize; + int maxprot; + int initprot; + uint nsects; + uint flags; +} +struct load_command +{ + uint cmd; + uint cmdsize; +} +enum { + SECTION_TYPE, + LC_SEGMENT_64 = 0x19, + S_THREAD_LOCAL_VARIABLES = 0x13 +} +extern (C) size_t malloc_size(const void* ptr) nothrow @nogc; + +/// Represents a TLS range. +struct TLSRange +{ + /// The start of the range. + void* start; + + /// The size of the range. + size_t size; + + /// Returns `true` if the range is valid. + bool isValid() const pure nothrow @nogc @safe + { + return start !is null && size > 0; + } + + /// Returns the range as an array. + void[] toArray() pure nothrow @nogc + { + return start[0 .. size]; + } +} + +/// Returns the TLS range of the current image. +TLSRange getTLSRange() nothrow @nogc +{ + static ubyte tlsAnchor; + const tlsSymbol = &tlsAnchor; + + foreach (i ; 0 .. _dyld_image_count) + { + const header = cast(const(mach_header_64)*) _dyld_get_image_header(i); + auto tlvInfo = tlvInfo(header); + + if (tlvInfo.foundTLSRange(tlsSymbol)) + return TLSRange(tlvInfo.tlv_addr, tlvInfo.tlv_size); + } + + return TLSRange.init; +} + +/** + * Returns `true` if the correct TLS range was found. + * + * If the given `info` is located in the same image as the given `tlsSymbol` + * this will return `true`. + * + * Params: + * info = the TLV info containing the TLV base address + * tlsSymbol = the TLS symbol to search for + * + * Returns: `true` if the correct TLS range was found + */ +bool foundTLSRange(const ref dyld_tlv_info info, const void* tlsSymbol) pure nothrow @nogc +{ + return info.tlv_addr <= tlsSymbol && + tlsSymbol < (info.tlv_addr + info.tlv_size); +} + + +/// TLV info. +struct dyld_tlv_info +{ + /// sizeof(dyld_tlv_info) + size_t info_size; + + /// Base address of TLV storage + void* tlv_addr; + + // Byte size of TLV storage + size_t tlv_size; +} + +/** + * Returns the TLV info for the given image. + * + * Asserts if no TLV address could be found. + * + * Params: + * header = the image to look for the TLV info in + * + * Returns: the TLV info + */ +dyld_tlv_info tlvInfo(const mach_header_64* header) nothrow @nogc +{ + auto tlvAddress = pthread_getspecific(header.firstTLVKey); + assert(tlvAddress, "No TLV address found"); + + dyld_tlv_info info = { + info_size: dyld_tlv_info.sizeof, + tlv_addr: tlvAddress, + tlv_size: malloc_size(tlvAddress) + }; + + return info; +} + +/** + * Returns the first TLV key for the given image. + * + * The TLV key is a key that associates a value of type `dyld_tlv_info` with a + * thread. Each thread local variable has an associates TLV key. The TLV keys + * are all the same for each image. + * + * Params: + * header = the image to look for the TLV key in + * + * Returns: the first TLV key for the given image or `pthread_key_t.max` if no + * key was found. + */ +pthread_key_t firstTLVKey(const mach_header_64* header) pure nothrow @nogc +{ + intptr_t slide = 0; + bool slideComputed = false; + const size = mach_header_64.sizeof; + auto command = cast(const(load_command)*)(cast(ubyte*) header + size); + + foreach (_; 0 .. header.ncmds) + { + if (command.cmd == LC_SEGMENT_64) + { + auto segment = cast(const segment_command_64*) command; + + if (!slideComputed && segment.filesize != 0) + { + slide = cast(uintptr_t) header - segment.vmaddr; + slideComputed = true; + } + + foreach (const ref section; segment.sections) + { + if ((section.flags & SECTION_TYPE) != S_THREAD_LOCAL_VARIABLES) + continue; + + return section.firstTLVDescriptor(slide).key; + } + } + + command = cast(const(load_command)*)(cast(ubyte*) command + command.cmdsize); + } + + return pthread_key_t.max; +} + +/** + * Returns the first TLV descriptor of the given section. + * + * Params: + * section = the section to get the TLV descriptor from + * slide = the slide + * + * Returns: the TLV descriptor + */ +const(tlv_descriptor)* firstTLVDescriptor(const ref section_64 section, intptr_t slide) pure nothrow @nogc +{ + return cast(const(tlv_descriptor)*)(section.addr + slide); +} + +/** + * Returns the sections of the given segment. + * + * Params: + * segment = the segment to get the sections from + * + * Returns: the sections. + */ +const(section_64)[] sections(const segment_command_64* segment) pure nothrow @nogc +{ + const size = segment_command_64.sizeof; + const firstSection = cast(const(section_64)*)(cast(ubyte*) segment + size); + return firstSection[0 .. segment.nsects]; +}