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
|
; 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:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Check validity of argument ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Return EINVAL if rdi is NULL or invalid
mov eax, -22 ; (EINVAL = -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].
mov ecx, [rdi + 8]
cmp ecx, 0xCAFEBABE
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
mov eax, SYS_GETTID
; gettid: rax = gettid()
syscall
; Save a copy of our TID (no need for an error check)
mov edx, eax
; Return EPERM if this lock currently doesn't belong to us
mov eax, -1 ; (EPERM = -1)
; Read the futex dword at [rdi] and keep its lowest 30 bits
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]; }
xor ecx, ecx
lock cmpxchg [rdi], ecx
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 esi, (FUTEX_UNLOCK_PI | FUTEX_PRIVATE_FLAG) ; futex: futex_op
; 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
mov eax, SYS_FUTEX
; futex: rax = futex(rdi, rsi, (rdx), (r10), (r8), (r9))
syscall
; Check result of futex: nonzero means failure
test rax, rax
jnz release_return
release_success:
xor eax, eax
release_return:
ret
|