Details
- Reviewers
jhb jrtc27 mmel andrew - Commits
- rG76f5fccc5fbd: amd64 rtld: implement support for TLSDESC relocation
rGd45d7aea6197: i386 rtld: implement support for TLSDESC relocation
rGfb63a60d6e15: rtld i386: use macro for the 'GNU ABI' calling convention of TLS resolver
rG5d9e5f49647e: rtld: explain the use of rtld_bind_lock in rtld_get_addr_slow()
rG6a69d28ee075: rtld: allow arches to initialize/finalize objects
Diff Detail
- Repository
- rG FreeBSD src repository
- Lint
Lint Not Applicable - Unit
Tests Not Applicable
Event Timeline
I looked at the implementation for aarch64 and I see some issues that I do not understand.
First, the problem seems to be that allocated tls_data AKA rtld_tlsdesc_dynamic argument structures are never freed. This is probably not too serious because typical program does not contain too many thread-local data and does not load/unload modules containing such data.
Second, is that tlsdesc->dtv_gen is initialized with the current tls_dtv_generation. Then, if any module is loaded afterward, the check in the inlined path for dtv->dtv_gen == tlsdata->dtv_gen always fail, so the code falls to the half of the slow path. I mean 'half' because it saves/restores all registers, and calls tls_get_addr_common(), which normally would observe dtv->dtv_gen == tls_dtv_generation and do not need to call _slow() resolver. But still, it seems unoptimal after the efforts to write the inline asm version.
Do I mis-read the code?
Remove dtv_gen from tlsdesc_dynarg. There is no point of storing the generation there for our dtv update structure.
Add comment explaining the use of rtld_bind_lock in rtld_get_addr_slow().
I believe both to be true, yes (and have previously deduced the latter myself, too...)
| libexec/rtld-elf/rtld.c | ||
|---|---|---|
| 5468 ↗ | (On Diff #185598) | This is specifically about why it needs to be exclusive not shared, right? I don't think it's the most clear it's talking about that, rather than taking the lock in general (which you need to synchronise with map_object bumping tls_max_index/tls_dtv_generation). |
Looking at the glibc source, they do have an equivalent to the dtv_gen field, but the fast path check is whether the DTV's generation is >= that field, not ==. I think this is a time optimisation that avoids the need to access the global generation in order to do the check, instead just using a copy that's more to hand, and also lets you avoid taking the slow path for DTV entries that don't actually need it, even though the generation counter is ahead (e.g. if you dlopen A, use TLS for A, dlopen B, then use TLS for A again, you can still use the fast path, but ye olde __tls_get_addr doesn't know that, so will pessimistically update the DTV for B's load).
Regarding leaking, glibc does seem to store them in a hash table tied to link_map (i.e. Obj_Entry) so will free on dlclose, unlike our arm64 implementation. Linked list seems a bit unnecessarily slow, but at the same time extensive dynamic TLSDESC + dlclose is probably not common enough to actually benefit from something like a reallocarray-based storage.
| libexec/rtld-elf/amd64/reloc.c | ||
|---|---|---|
| 159 ↗ | (On Diff #185599) | I do fear there's going to be a lot of copy/pasting of all this code for every architecture, when it's 99% the same (I also started a RISC-V implementation a while back but didn't get to the point of writing the tests for the dynamic slow path). I don't know how enthusiastic you would be about trying to unify the amd64, aarch64 and i386 implementations? |
Yes, something like that is described in the TSLDESC RFC. But you have to update the dtv gen in the TLSDESC arg structure at least once anyway. I think it is fine to skip this optimization.
The list is only traversed on dlclose() to free items, so I do not see it as causing any slowdown. In fact, where the hashed lookup would be useful?
I don't understand why they use a hash table to uniquify their dynamic tlsdesc structs. I only note that it's a less cache-hating data structure, but for our implementation if we wanted that we'd just use an array of pointers. But I suppose you still have the dynamic tlsdesc structs scattered throughout memory so pointer chasing the linked list doesn't really make it much worse, beyond a bit of extra data dependency. So I think the linked list is fine.
I believe that tlsdesc_args structures should be effectively allocated linearly. They are allocated from rtld malloc during the module load (not lazily), and the rtld malloc is relatively simple to be equiv to bump allocator while there is a space on the current page.