1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
; Under MIT license, see /LICENSE.txt
; Cheat sheet for Linux' x86_64 calling convention:
;
; - free to overwrite (caller should save them):
; rax, rcx, rdx, rsi, rdi, r8-r11, xmm0-xmm15
; - caller expects be kept (callee should save them):
; rbx, rbp, r12-r15
;
; - for passing paramters to functions:
; rdi, rsi, rdx, rcx, r8, r9, xmm0-xmm7
; - for getting return values from functions:
; rax, rdx, xmm0
;
; - for passing parameters to syscalls:
; rax, rdi, rsi, rdx, r10, r8, r9
; - for getting return values from syscalls:
; rax, rdx
; - overwritten by syscalls (all others preserved):
; rcx, r11
section .text
; Relevant system call IDs
%define SYS_GETTID 186
%define SYS_FUTEX 202
; Relevant operations for futex
%define FUTEX_UNLOCK_PI 7
%define FUTEX_PRIVATE_FLAG 0x80
; Relevant bits for futex dword
%define FUTEX_TID_MASK 0x3fffffff
%define FUTEX_OWNER_DIED 0x40000000
%define FUTEX_WAITERS 0x80000000
; Release an acquired lock if we're who acquired it. Argument:
; rdi: struct{u32,u32,u32}* = handle of lock to release
; Returns zero on success, or a standard error code.
global linen_lock_release
linen_lock_release:
; It's handy to have a register that's 0 during most of this function
xor esi, esi
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Check validity of argument ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Return EINVAL (-22) if rdi is NULL or invalid
lea eax, [rsi - 22] ; mov eax, -22
test rdi, rdi
jz release_return ; rdi is NULL
; rdi is nonzero, so let's just assume it's a valid pointer;
; if that assumption is wrong we'll get a segmentation fault.
; But we don't yet trust that [rdi] is a valid lock handle!
; To verify this we check the canary value stored at [rdi + 8].
cmp dword [rdi + 8], 0xCAFEBABE ; Oh CISC...
jnz release_return
; Lock owners are identified by their TID; let's find ours.
; The gettid system call simply returns our Linux thread ID.
; See: man 2 gettid
; gettid: rax = system call ID
xor eax, eax
mov al, SYS_GETTID
; gettid: rax = gettid()
syscall
; Save a copy of our TID (no need for an error check)
mov edx, eax
; Return EPERM (-1) if this lock currently doesn't belong to us
or eax, -1 ; mov eax, -1
; Read the futex dword at [rdi] and keep its lowest 30 bits.
; No need to use atomics, since we currently own this lock.
mov ecx, [rdi]
and ecx, FUTEX_TID_MASK
; Those bits contain the owner's TID; it should be our TID
cmp ecx, edx
jne release_return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; (Partially) release our lock ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Decrement the recursion counter. If it's still > 1, we're done here.
dec dword [rdi + 4]
jnz release_success
; If it reaches 0, it's time for a full release by setting [rdi] to 0.
; Restore our saved TID to eax for "lock cmpxchg" below
mov eax, edx
; Atomically try to set the dword at [rdi] to 0 if it was equal to our TID.
; if ([rdi] == eax) { [rdi] = 0; goto release_success; } else { eax = [rdi]; }
lock cmpxchg [rdi], esi ; esi = 0
je release_success
; We failed because [rdi] wasn't equal to our TID. In theory,
; that can mean only one thing: [rdi] = (edx | FUTEX_WAITERS).
; In that case we need to ask the kernel to wake up the threads
; who are waiting (via a futex system call) for [rdi] to change.
; See: man 2 futex
; futex: rdi = uaddr: address of the dword to announce for
; futex: rsi = futex_op: which futex operation we want:
; - FUTEX_UNLOCK_PI: wake up one thread sleeping via FUTEX_LOCK_PI
; - FUTEX_PRIVATE_FLAG: this lock isn't shared with another process
mov sil, (FUTEX_UNLOCK_PI | FUTEX_PRIVATE_FLAG)
; futex: rdx = val: ignored when FUTEX_UNLOCK_PI is used
; futex: r10 = timeout: ignored when FUTEX_UNLOCK_PI is used
; futex: r8 = uaddr2: ignored when FUTEX_UNLOCK_PI is used
; futex: r9 = val3: ignored when FUTEX_UNLOCK_PI is used
; futex: rax = system call ID
xor eax, eax
mov al, SYS_FUTEX
; futex: rax = futex(rdi, rsi, (rdx), (r10), (r8), (r9))
syscall
; Check result of futex: nonzero means failure
test eax, eax
jnz release_return
release_success:
xor eax, eax
release_return:
ret
|