summaryrefslogtreecommitdiff
path: root/src/lock_release.asm
blob: f86caa2f13c7e313c2ae9c6c1ec60ba0ab754a49 (plain)
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