| ... | ... | @@ -14,7 +14,8 @@ const mem = std.mem; |
| 14 | 14 | const elf = std.elf; |
| 15 | 15 | const math = std.math; |
| 16 | 16 | const assert = std.debug.assert; |
| 17 | | const native_arch = @import("builtin").cpu.arch; |
| 17 | const builtin = @import("builtin"); |
| 18 | const native_arch = builtin.cpu.arch; |
| 18 | 19 | const linux = std.os.linux; |
| 19 | 20 | const page_size_min = std.heap.page_size_min; |
| 20 | 21 | |
| ... | ... | @@ -41,13 +42,13 @@ const Variant = enum { |
| 41 | 42 | I_original, |
| 42 | 43 | /// The modified Variant I: |
| 43 | 44 | /// |
| 44 | | /// --------------------------------------------------- |
| 45 | | /// | DTV | Zig TCB | ABI TCB | [Offset] | TLS Blocks | |
| 46 | | /// -------------------------------------^------------- |
| 47 | | /// `-- The TP register points here. |
| 45 | /// -------------------------------------------- |
| 46 | /// | DTV | Zig TCB | ABI TCB | TLS Blocks | |
| 47 | /// ------------------------------^------------- |
| 48 | /// `-- The TP register points here (*inside* the TLS blocks). |
| 48 | 49 | /// |
| 49 | | /// The offset (which can be zero) is applied to the TP only; there is never a physical gap |
| 50 | | /// between the ABI TCB and the TLS blocks. This implies that we only need to align the TP. |
| 50 | /// The offset from the start of the TLS blocks to the TP register is `current_tp_offset`. It |
| 51 | /// may be zero, in which case the TP register points to the start of the TLS blocks. |
| 51 | 52 | /// |
| 52 | 53 | /// The first (and only) word in the ABI TCB points to the DTV. |
| 53 | 54 | I_modified, |
| ... | ... | @@ -106,7 +107,7 @@ const current_variant: Variant = switch (native_arch) { |
| 106 | 107 | else => @compileError("undefined TLS variant for this architecture"), |
| 107 | 108 | }; |
| 108 | 109 | |
| 109 | | /// The Offset value for the modified Variant I. |
| 110 | /// The offset value for the modified Variant I. |
| 110 | 111 | const current_tp_offset = switch (native_arch) { |
| 111 | 112 | .m68k, |
| 112 | 113 | .mips, |
| ... | ... | @@ -379,6 +380,106 @@ pub fn setThreadPointer(addr: usize) void { |
| 379 | 380 | } |
| 380 | 381 | } |
| 381 | 382 | |
| 383 | pub fn getThreadPointer() usize { |
| 384 | @setRuntimeSafety(false); |
| 385 | @disableInstrumentation(); |
| 386 | |
| 387 | return switch (native_arch) { |
| 388 | .aarch64, .aarch64_be => asm ( |
| 389 | \\ mrs %[ret], tpidr_el0 |
| 390 | : [ret] "=r" (-> usize), |
| 391 | ), |
| 392 | .alpha => asm ( |
| 393 | \\ rduniq |
| 394 | : [ret] "={$0}" (-> usize), |
| 395 | ), |
| 396 | .arc, .arceb => asm ( |
| 397 | \\ mov %[ret], r25 |
| 398 | : [ret] "=r" (-> usize), |
| 399 | ), |
| 400 | .arm, .armeb, .thumb, .thumbeb => asm ( |
| 401 | \\ mrc p15, 0, %[ret], c13, c0, 3 |
| 402 | : [ret] "=r" (-> usize), |
| 403 | ), |
| 404 | .csky => asm ( |
| 405 | \\ mov %[ret], r31 |
| 406 | : [ret] "=r" (-> usize), |
| 407 | ), |
| 408 | .hexagon => asm ( |
| 409 | \\ %[ret] = ugp |
| 410 | : [ret] "=r" (-> usize), |
| 411 | ), |
| 412 | .hppa => asm ( |
| 413 | \\ mfctl %%cr27, %[ret] |
| 414 | : [ret] "=r" (-> usize), |
| 415 | ), |
| 416 | .loongarch32, .loongarch64 => asm ( |
| 417 | \\ move %[ret], $tp |
| 418 | : [ret] "=r" (-> usize), |
| 419 | ), |
| 420 | .m68k => linux.syscall1(.get_thread_area), |
| 421 | .mips, .mipsel, .mips64, .mips64el => asm ( |
| 422 | \\ rdhwr %[ret], $29 |
| 423 | : [ret] "=r" (-> usize), |
| 424 | ), |
| 425 | .microblaze, .microblazeel => asm ( |
| 426 | \\ ori %[ret], r21, 0 |
| 427 | : [ret] "=r" (-> usize), |
| 428 | ), |
| 429 | .or1k => asm ( |
| 430 | \\ l.ori %[ret], r10, 0 |
| 431 | : [ret] "=r" (-> usize), |
| 432 | ), |
| 433 | .riscv32, .riscv64 => asm ( |
| 434 | \\ mv %[ret], tp |
| 435 | : [ret] "=r" (-> usize), |
| 436 | ), |
| 437 | .powerpc, .powerpcle => asm ( |
| 438 | \\ mr %[ret], 2 |
| 439 | : [ret] "=r" (-> usize), |
| 440 | ), |
| 441 | .powerpc64, .powerpc64le => asm ( |
| 442 | \\ mr %[ret], 13 |
| 443 | : [ret] "=r" (-> usize), |
| 444 | ), |
| 445 | .s390x => asm ( |
| 446 | \\ ear %[ret], %%a0 |
| 447 | \\ sllg %[ret], %[ret], 32 |
| 448 | \\ ear %[ret], %%a1 |
| 449 | : [ret] "=r" (-> usize), |
| 450 | ), |
| 451 | .sh, .sheb => asm ( |
| 452 | \\ stc %[ret], gbr |
| 453 | : [ret] "=r" (-> usize), |
| 454 | ), |
| 455 | .sparc, .sparc64 => asm ( |
| 456 | \\ mov %%g7, %[ret] |
| 457 | : [ret] "=r" (-> usize), |
| 458 | ), |
| 459 | .x86 => asm ( |
| 460 | \\ movl %%gs:0, %[ret] |
| 461 | : [ret] "=r" (-> usize), |
| 462 | ), |
| 463 | .x86_64 => switch (@sizeOf(usize)) { |
| 464 | 8 => asm ( |
| 465 | \\ movq %%fs:0, %[ret] |
| 466 | : [ret] "=r" (-> usize), |
| 467 | ), |
| 468 | // On x32, usize is 32 bits. |
| 469 | 4 => asm ( |
| 470 | \\ movl %%fs:0, %[ret] |
| 471 | : [ret] "=r" (-> usize), |
| 472 | ), |
| 473 | else => comptime unreachable, |
| 474 | }, |
| 475 | .xtensa, .xtensaeb => asm ( |
| 476 | \\ rur %[ret], threadptr |
| 477 | : [ret] "=r" (-> usize), |
| 478 | ), |
| 479 | else => @compileError("Unsupported architecture"), |
| 480 | }; |
| 481 | } |
| 482 | |
| 382 | 483 | fn computeAreaDesc(phdrs: []elf.Phdr) void { |
| 383 | 484 | @setRuntimeSafety(false); |
| 384 | 485 | @disableInstrumentation(); |
| ... | ... | @@ -616,3 +717,32 @@ inline fn mmap_tls(length: usize) usize { |
| 616 | 717 | }); |
| 617 | 718 | } |
| 618 | 719 | } |
| 720 | |
| 721 | comptime { |
| 722 | assert(!builtin.link_libc); // otherwise libc should control TLS |
| 723 | |
| 724 | if (builtin.output_mode == .Exe and builtin.link_mode == .static) { |
| 725 | // This is a static executable without libc, so it is our job to provide the TLS accessor |
| 726 | // function for the GD and LD models. This function is unlikely to actually be used, since |
| 727 | // the linker should be able to relax every TLS access to the LE model and therefore |
| 728 | // eliminate all calls to this function, but that isn't guaranteed. |
| 729 | _ = struct { |
| 730 | const TlsIndex = switch (native_arch) { |
| 731 | .x86_64 => extern struct { module: u64, offset: u64 }, // Even for x32... |
| 732 | else => extern struct { module: usize, offset: usize }, // ...but not MIPS N32! |
| 733 | }; |
| 734 | export fn __tls_get_addr(ti: *const TlsIndex) *anyopaque { |
| 735 | assert(ti.module == 1); // The executable's module ID is always 1 |
| 736 | const tp = getThreadPointer(); |
| 737 | const block: [*]u8 = switch (current_variant) { |
| 738 | .I_original => @ptrFromInt(tp -% area_desc.abi_tcb.offset +% area_desc.block.offset), |
| 739 | .I_modified => @ptrFromInt(tp -% current_tp_offset), |
| 740 | // The `.I_original` approach would also work for `.II`, but there is an |
| 741 | // alternative strategy which is one less operation: |
| 742 | .II => @ptrFromInt(tp -% area_desc.block.size), |
| 743 | }; |
| 744 | return block[@intCast(ti.offset)..]; |
| 745 | } |
| 746 | }; |
| 747 | } |
| 748 | } |