Skip to content
This repository has been archived by the owner on Feb 8, 2024. It is now read-only.

Commit

Permalink
Fix issue 20019: Symbol not found: _dyld_enumerate_tlv_storage on mac…
Browse files Browse the repository at this point in the history
…OS 10.15

Ported from 378fc6d

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.
  • Loading branch information
lionello committed Feb 13, 2020
1 parent 694089c commit f724c08
Showing 1 changed file with 210 additions and 14 deletions.
224 changes: 210 additions & 14 deletions src/rt/sections_ldc.d
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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)
{
Expand Down Expand Up @@ -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];
}

0 comments on commit f724c08

Please sign in to comment.