Source code for cle.backends.tls.elf_tls

"""
This module is used when parsing the Thread Local Storage of an ELF binary. It heavily uses the TLSArchInfo
namedtuple from archinfo.

ELF TLS is implemented based on the following documents:

    - https://www.uclibc.org/docs/tls.pdf
    - https://www.uclibc.org/docs/tls-ppc.txt
    - https://www.uclibc.org/docs/tls-ppc64.txt
    - https://www.linux-mips.org/wiki/NPTL
"""

from .tls_object import InternalTLSRelocation, ThreadManager, TLSObject

TLS_BLOCK_ALIGN = 0x10
TLS_TOTAL_HEAD_SIZE = 0x4000
TLS_HEAD_ALIGN = 0x10000


[docs]def roundup(val, to=TLS_BLOCK_ALIGN): return val - 1 + (to - ((val - 1) % to))
[docs]class ELFThreadManager(ThreadManager):
[docs] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.used_data = 0
[docs] def register_object(self, obj): if not super().register_object(obj): return False # only track tls_block_offset for modules registered before the first thread # ??? is this right if not self.threads: # tls_block_offset has these semantics: the thread pointer plus the block offset equals the address of the # module's TLS data in memory if self.arch.elf_tls.variant == 1: obj.tls_block_offset = self.used_data else: obj.tls_block_offset = -self.used_data - roundup(obj.tls_block_size) self.used_data += roundup(obj.tls_block_size) return True
@property def _thread_cls(self): if self.arch.elf_tls.variant == 1: return ELFTLSObjectV1 else: return ELFTLSObjectV2
[docs]class ELFTLSObject(TLSObject):
[docs] def __init__(self, thread_manager: ELFThreadManager): super().__init__(loader=thread_manager.loader, arch=thread_manager.arch) self.tcb_offset: int = None self.dtv_offset: int = None self.tp_offset: int = None self.head_offset: int = None self._max_addr: int = None self.tlsoffsets = [obj.tls_block_offset for obj in thread_manager.modules] self.pic = True self._calculate_pointers(thread_manager.used_data, thread_manager.max_modules) # add backer for header self.memory.add_backer(self.head_offset, bytes(TLS_TOTAL_HEAD_SIZE)) # add backer for dtv self.memory.add_backer( self.dtv_offset - 2 * self.arch.bytes, bytes(2 * self.arch.bytes * thread_manager.max_modules + 2 * self.arch.bytes), ) # Set the appropriate pointers in the tcbhead for off in self.arch.elf_tls.head_offsets: self._drop_int(off + self.tcb_offset, self.tp_offset, True) for off in self.arch.elf_tls.dtv_offsets: self._drop_int(off + self.tcb_offset, self.dtv_offset, True) for off in self.arch.elf_tls.pthread_offsets: self._drop_int(off + self.tcb_offset, self.tp_offset, True) # ????? # tid. feel free to move this code wherever # this only matters if you're not running the libc initializers... hm. # tid = len(thread_manager.threads) + 1 # if self.arch.name == 'AMD64': # self._drop_int(self.tcb_offset + 0x2d0, tid, False, size=4) # Set up the DTV # at dtv[-1] there's capacity, at dtv[0] there's count (technically generation number?) self._drop_int(self.dtv_offset - 2 * self.arch.bytes, thread_manager.max_modules - 1) self._drop_int(self.dtv_offset, len(thread_manager.modules)) # set up each module for obj in thread_manager.modules: # dtv entry dtv_entry_offset = self.dtv_offset + 2 * self.arch.bytes * obj.tls_module_id self._drop_int( dtv_entry_offset, self.tp_offset + obj.tls_block_offset + self.arch.elf_tls.dtv_entry_offset, True ) self._drop_int(dtv_entry_offset + self.arch.bytes, 1) # initialization image image = thread_manager.initialization_image(obj) if image is None: continue self.memory.add_backer(self.tp_offset + obj.tls_block_offset, image)
def _calculate_pointers(self, used_data, max_modules): raise NotImplementedError def _drop_int(self, offset, num, needs_relocation=False, **kwargs): if needs_relocation: self.relocs.append(InternalTLSRelocation(num, offset, self)) self.memory.pack_word(offset, num, **kwargs) @property def thread_pointer(self): """ The thread pointer. This is a technical term that refers to a specific location in the TLS segment. """ return self.mapped_base + self.tp_offset @property def user_thread_pointer(self): """ The thread pointer that is exported to the user """ return self.thread_pointer + self.arch.elf_tls.tp_offset @property def max_addr(self): return self.mapped_base + self._max_addr
[docs] def get_addr(self, module_id, offset): """ basically ``__tls_get_addr``. """ return self.memory.unpack_word(self.dtv_offset + module_id * self.arch.bytes) + offset
[docs]class ELFTLSObjectV1(ELFTLSObject): # variant 1: memory is laid out like so: # [header][module data] # ^ thread pointer def _calculate_pointers(self, used_data, max_modules): self.tcb_offset = TLS_TOTAL_HEAD_SIZE - self.arch.elf_tls.tcbhead_size # CRITICAL DIFFERENCE FROM THE DOC - variant 1 seems to expect the thread pointer points to the end of the TCB self.tp_offset = TLS_TOTAL_HEAD_SIZE self.dtv_offset = TLS_TOTAL_HEAD_SIZE + used_data + 2 * self.arch.bytes self.head_offset = 0 # ^^ that's the point of this field self._max_addr = self.dtv_offset + 2 * self.arch.bytes * max_modules - 1
[docs]class ELFTLSObjectV2(ELFTLSObject): # variant 2: memory is laid out like so: # [module data][header] # ^ thread pointer def _calculate_pointers(self, used_data, max_modules): self.tcb_offset = roundup(used_data, TLS_HEAD_ALIGN) self.tp_offset = roundup(used_data, TLS_HEAD_ALIGN) self.dtv_offset = self.tp_offset + TLS_TOTAL_HEAD_SIZE + 2 * self.arch.bytes self.head_offset = self.tp_offset self._max_addr = self.dtv_offset + 2 * self.arch.bytes * max_modules - 1