mirror of
https://github.com/yugr/Implib.so
synced 2026-06-08 18:33:41 +00:00
273 lines
7.9 KiB
Plaintext
273 lines
7.9 KiB
Plaintext
---- MODULE Init ----
|
|
|
|
(*
|
|
TODO:
|
|
- allow nested function calls ?
|
|
*)
|
|
|
|
EXTENDS
|
|
Integers, FiniteSets, Sequences, TLC
|
|
|
|
CONSTANTS
|
|
THREADS, FUNS, CALLS, MAX_DEPTH, MAX_LOCK
|
|
|
|
ASSUME
|
|
/\ Cardinality(FUNS) > 0
|
|
/\ CALLS \in Nat
|
|
/\ MAX_DEPTH \in Nat \ {0}
|
|
|
|
NoThread == CHOOSE t : t \notin THREADS
|
|
|
|
VARIABLES
|
|
\* Thread execution states
|
|
threads,
|
|
\* Whether function address has been initialized
|
|
shim_table,
|
|
\* Whether library handle is initialized
|
|
lib_handle,
|
|
\* Current state the library is in
|
|
lib_state,
|
|
\* Initialization lock
|
|
rec_lock
|
|
|
|
\* STATES
|
|
|
|
PC == {
|
|
(* Driver program *) "START", "DRIVER",
|
|
(* At start of interceptor *) "SHIM_START",
|
|
(* In real function *) "FUNC_START", "FUNC_END",
|
|
(* At start of resolver *) "RESOLVER_START",
|
|
(* After locking mutex *) "LOCKED",
|
|
(* Inside dlopen and global ctor *) "DLOPENING",
|
|
(* After dlopen, before writing lib_handle *) "DLOPENED",
|
|
(* After writing lib_handle *) "WROTE_HANDLE",
|
|
(* After dlsym *) "RESOLVED",
|
|
(* At end of resolver *) "UNLOCKED"
|
|
}
|
|
|
|
LibraryState == {
|
|
"UNLOADED", "LOADING", "LOADED"
|
|
}
|
|
|
|
Callees == FUNS \union {""}
|
|
|
|
StackFrame == [pc : PC, calls : 0..CALLS, callee : Callees]
|
|
|
|
CallStack == UNION {[1..len -> StackFrame] : len \in 0..MAX_DEPTH}
|
|
|
|
|
|
\* HELPERS
|
|
|
|
Last(s) == s[Len(s)]
|
|
|
|
Goto(t, pc) == [threads EXCEPT ![t] = [
|
|
@ EXCEPT ![Len(@)] = [
|
|
@ EXCEPT !.pc = pc]
|
|
]
|
|
]
|
|
|
|
\* INVARIANTS AND PROPERTIES
|
|
|
|
TypeInvariant ==
|
|
/\ threads \in [THREADS -> CallStack]
|
|
/\ shim_table \in [FUNS -> BOOLEAN]
|
|
/\ lib_handle \in BOOLEAN
|
|
/\ lib_state \in LibraryState
|
|
/\ rec_lock \in [owner : THREADS \union {NoThread}, count : 0..MAX_LOCK]
|
|
|
|
LockInvariant ==
|
|
/\
|
|
\/ (rec_lock.owner = NoThread /\ rec_lock.count = 0)
|
|
\/ (rec_lock.owner # NoThread /\ rec_lock.count > 0)
|
|
/\
|
|
\A t \in THREADS :
|
|
\/ Len(threads[t]) = 0
|
|
\/ Last(threads[t]).pc \in {"LOCKED", "DLOPENING", "DLOPENED", "WROTE_HANDLE", "RESOLVED"} => rec_lock.owner = t
|
|
|
|
\* Library must be initialized before clients can call any of its functions via fast path
|
|
LoadBeforeUse ==
|
|
\A f \in FUNS : shim_table[f] => lib_state = "LOADED"
|
|
LoadBeforeUse2 ==
|
|
\A t \in THREADS :
|
|
\/ Len(threads[t]) = 0
|
|
\/ Last(threads[t]).pc = "FUNC_START" => lib_state \in (IF Len(threads[t]) > 1 THEN {"LOADING", "LOADED"} ELSE {"LOADED"})
|
|
|
|
\* Library handle set only if library is loaded (not necessarily initialized)
|
|
LibHandleCorrectness == lib_handle => lib_state \in {"LOADING", "LOADED"}
|
|
|
|
\* All threads terminate, lock is released and library is loaded
|
|
Termination == <>[]
|
|
/\ \A t \in THREADS : Len(threads[t]) = 0
|
|
/\ rec_lock.owner = NoThread /\ rec_lock.count = 0
|
|
/\ lib_state = "LOADED" /\ lib_handle
|
|
|
|
\* Library never UN-loaded
|
|
NoLibResets ==
|
|
[][
|
|
/\ lib_state = "LOADING" => lib_state' \in {"LOADING", "LOADED"}
|
|
/\ lib_state = "LOADED" => lib_state' = "LOADED"
|
|
]_lib_state
|
|
|
|
\* Shims are never reset
|
|
NoShimResets ==
|
|
[][
|
|
\A f \in FUNS : shim_table[f] => shim_table'[f]
|
|
]_shim_table
|
|
|
|
\* SPECIFICATION
|
|
|
|
Init ==
|
|
/\ threads = [t \in THREADS |-> <<[pc |-> "START", calls |-> CALLS, callee |-> ""]>>]
|
|
/\ shim_table = [f \in FUNS |-> FALSE]
|
|
/\ lib_handle = FALSE
|
|
/\ lib_state = "UNLOADED"
|
|
/\ rec_lock = [owner |-> NoThread, count |-> 0]
|
|
|
|
\* Thread starts execution
|
|
Start(t) ==
|
|
/\ Last(threads[t]).pc = "START"
|
|
/\ threads' = Goto(t, "DRIVER")
|
|
/\ UNCHANGED <<shim_table, lib_handle, lib_state, rec_lock>>
|
|
|
|
\* Thread calls shim
|
|
Call(t) ==
|
|
/\ Last(threads[t]).pc = "DRIVER"
|
|
/\ Last(threads[t]).calls > 0
|
|
/\ \E f \in FUNS :
|
|
/\ threads' = [threads EXCEPT ![t] = [
|
|
@ EXCEPT ![Len(@)] = [
|
|
pc |-> "SHIM_START", calls |-> @.calls - 1, callee |-> f]
|
|
]
|
|
]
|
|
/\ UNCHANGED <<shim_table, lib_handle, lib_state, rec_lock>>
|
|
|
|
\* Shim is already resolved so call real function
|
|
FastPath(t) ==
|
|
/\ Last(threads[t]).pc = "SHIM_START"
|
|
/\ shim_table[Last(threads[t]).callee]
|
|
/\ threads' = Goto(t, "FUNC_START")
|
|
/\ UNCHANGED <<shim_table, lib_handle, lib_state, rec_lock>>
|
|
|
|
\* Shim is unresolved so goto resolver
|
|
SlowPath(t) ==
|
|
/\ Last(threads[t]).pc = "SHIM_START"
|
|
/\ ~shim_table[Last(threads[t]).callee]
|
|
/\ threads' = Goto(t, "RESOLVER_START")
|
|
/\ UNCHANGED <<shim_table, lib_handle, lib_state, rec_lock>>
|
|
|
|
\* Enter critical section
|
|
Lock(t) ==
|
|
/\ Last(threads[t]).pc = "RESOLVER_START"
|
|
/\ (rec_lock.owner = t \/ rec_lock.count = 0)
|
|
/\ threads' = Goto(t, "LOCKED")
|
|
/\ rec_lock' = [owner |-> t, count |-> rec_lock.count + 1]
|
|
/\ UNCHANGED <<shim_table, lib_handle, lib_state>>
|
|
|
|
\* Load library after handle is already set (simplest case)
|
|
LoadLibrarySimple(t) ==
|
|
/\ Last(threads[t]).pc = "LOCKED"
|
|
/\ lib_handle
|
|
/\ threads' = Goto(t, "RESOLVED")
|
|
/\ UNCHANGED <<shim_table, lib_handle, lib_state, rec_lock>>
|
|
|
|
\* Load library for the first time, running global ctors
|
|
LoadLibraryFirstTime(t) ==
|
|
/\ Last(threads[t]).pc = "LOCKED"
|
|
/\ ~lib_handle
|
|
/\ lib_state = "UNLOADED"
|
|
/\ threads' = [threads EXCEPT ![t] = Append(
|
|
[@ EXCEPT ![Len(@)] = [@ EXCEPT !.pc = "DLOPENING"]],
|
|
[pc |-> "DRIVER", calls |-> CALLS, callee |-> ""]
|
|
)]
|
|
/\ lib_state' = "LOADING"
|
|
/\ UNCHANGED <<shim_table, lib_handle, rec_lock>>
|
|
|
|
\* Initialize library
|
|
\* TODO: LOADED may be set before DLOPENED
|
|
InitializeLibrary(t) ==
|
|
/\ Last(threads[t]).pc = "DLOPENING"
|
|
/\ lib_state = "LOADING"
|
|
/\ threads' = Goto(t, "DLOPENED")
|
|
/\ lib_state' = "LOADED"
|
|
/\ UNCHANGED <<shim_table, lib_handle, rec_lock>>
|
|
|
|
\* Load library after loaded but before handle is set
|
|
\* Note that this corresponds to two cases: 1) library still initializing, 2) library initialized
|
|
LoadLibraryRecursive(t) ==
|
|
/\ Last(threads[t]).pc = "LOCKED"
|
|
/\ ~lib_handle
|
|
/\ lib_state \in {"LOADING", "LOADED"}
|
|
/\ threads' = Goto(t, "DLOPENED")
|
|
/\ UNCHANGED <<shim_table, lib_handle, lib_state, rec_lock>>
|
|
|
|
\* Set lib_handle
|
|
WriteHandle(t) ==
|
|
/\ Last(threads[t]).pc = "DLOPENED"
|
|
/\ threads' = Goto(t, "WROTE_HANDLE")
|
|
/\ lib_handle' = TRUE
|
|
/\ UNCHANGED <<shim_table, lib_state, rec_lock>>
|
|
|
|
\* Resolve symbol
|
|
Resolve(t) ==
|
|
/\ Last(threads[t]).pc = "WROTE_HANDLE"
|
|
/\ threads' = Goto(t, "RESOLVED")
|
|
\* We publish only in first call (this is imprecise but ok for now)
|
|
/\ shim_table' = IF rec_lock.count > 1 THEN shim_table ELSE [shim_table EXCEPT ![Last(threads[t]).callee] = TRUE]
|
|
/\ UNCHANGED <<lib_handle, lib_state, rec_lock>>
|
|
|
|
\* Exit critical section
|
|
Unlock(t) ==
|
|
/\ Last(threads[t]).pc = "RESOLVED"
|
|
/\ threads' = Goto(t, "UNLOCKED")
|
|
/\ rec_lock' = IF rec_lock.count = 1 THEN [owner |-> NoThread, count |-> 0] ELSE [owner |-> rec_lock.owner, count |-> rec_lock.count - 1]
|
|
/\ UNCHANGED <<shim_table, lib_handle, lib_state>>
|
|
|
|
\* Call after resolution
|
|
ReCall(t) ==
|
|
/\ Last(threads[t]).pc = "UNLOCKED"
|
|
/\ threads' = Goto(t, "FUNC_START")
|
|
/\ UNCHANGED <<shim_table, lib_handle, lib_state, rec_lock>>
|
|
|
|
\* Thread returns from function
|
|
Return(t) ==
|
|
/\ Last(threads[t]).pc = "FUNC_START"
|
|
/\ threads' = [threads EXCEPT ![t] = [
|
|
@ EXCEPT ![Len(@)] = [
|
|
@ EXCEPT !.pc = "DRIVER", !.callee = ""]
|
|
]
|
|
]
|
|
/\ UNCHANGED <<shim_table, lib_handle, lib_state, rec_lock>>
|
|
|
|
\* Thread returns
|
|
PopFrame(t) ==
|
|
/\ Last(threads[t]).pc = "DRIVER"
|
|
/\ Last(threads[t]).calls = 0
|
|
/\ threads' = [threads EXCEPT ![t] = SubSeq(@, 1, Len(@) - 1)]
|
|
/\ UNCHANGED <<shim_table, lib_handle, lib_state, rec_lock>>
|
|
|
|
\* Program completes
|
|
Stop ==
|
|
/\ \A t \in THREADS : Len(threads[t]) = 0
|
|
/\ UNCHANGED <<threads, shim_table, lib_handle, lib_state, rec_lock>>
|
|
|
|
Next ==
|
|
\/ \E t \in THREADS :
|
|
/\ Len(threads[t]) > 0
|
|
/\ \/ Start(t)
|
|
\/ Call(t)
|
|
\/ FastPath(t)
|
|
\/ SlowPath(t)
|
|
\/ Lock(t)
|
|
\/ LoadLibrarySimple(t) \/ LoadLibraryFirstTime(t) \/ InitializeLibrary(t) \/ LoadLibraryRecursive(t) \/ WriteHandle(t)
|
|
\/ Resolve(t) \/ Unlock(t) \/ ReCall(t)
|
|
\/ Return(t)
|
|
\/ PopFrame(t)
|
|
\/ Stop
|
|
|
|
Spec ==
|
|
/\ Init
|
|
/\ [][Next]_<<threads, shim_table, lib_handle, lib_state, rec_lock>>
|
|
/\ WF_<<threads>>(Next)
|
|
|
|
======================================================================
|