Files
raven/pkg/ntobj/object.go
T

440 lines
13 KiB
Go

// Package ntobj implements the in-process NT object manager - the architectural
// replacement for Wine's out-of-process wineserver. It owns:
//
// - object lifetime via reference counting (NtClose / NtDuplicateObject),
// - the named-object namespace (\BaseNamedObjects\...),
// - the wait engine with correct Win32 semantics: WaitAny, atomic WaitAll
// (no lost wakeups on auto-reset objects), timeouts, and abandoned mutants.
//
// Everything is serialized on a single manager mutex with one broadcast
// condition variable. This is intentionally simple and obviously correct;
// performance (avoiding the thundering herd) is a later optimization. Because it
// is pure Go with no guest/CPU dependencies, it is fully unit-testable on its
// own - unlike the loader/emulator paths that need real binaries.
package ntobj
import (
"sync"
"time"
)
// Win32 wait return codes.
const (
WaitObject0 uint32 = 0x00000000
WaitAbandoned0 uint32 = 0x00000080
WaitTimeout uint32 = 0x00000102
WaitIOCompletion uint32 = 0x000000C0
WaitFailed uint32 = 0xFFFFFFFF
)
// Waitable is implemented by every kernel object that a thread can wait on.
// All methods are invoked with the owning Manager's lock held, so
// implementations must not lock or block.
type Waitable interface {
// signaled reports whether the object is currently acquirable by thread tid.
signaled(tid uint64) bool
// satisfy performs the state change of a successful wait (auto-reset event
// clears, semaphore decrements, mutant takes ownership). It is only called
// when signaled(tid) is true - and for WaitAll only when every object is
// simultaneously signaled. It reports whether the wait was satisfied by an
// abandoned mutant.
satisfy(tid uint64) (abandoned bool)
}
// Manager is the object manager instance (one per emulated machine/session).
type Manager struct {
mu sync.Mutex
cond *sync.Cond
names map[string]*Object
apcs map[uint64][]func() // per-thread user-APC queue (alertable waits)
}
// New creates an empty object manager.
func New() *Manager {
m := &Manager{names: make(map[string]*Object), apcs: make(map[uint64][]func())}
m.cond = sync.NewCond(&m.mu)
return m
}
// Object is a reference-counted kernel object. The payload carries the
// type-specific behavior; it may also implement Waitable.
type Object struct {
mgr *Manager
name string
refs int32
payload interface{}
onClose func()
}
// Payload returns the type-specific object behind this Object.
func (o *Object) Payload() interface{} { return o.payload }
// Name returns the object's namespace name ("" if anonymous).
func (o *Object) Name() string { return o.name }
// Refs returns the current reference count (for tests/diagnostics).
func (o *Object) Refs() int32 {
o.mgr.mu.Lock()
defer o.mgr.mu.Unlock()
return o.refs
}
// Create makes a new anonymous object with refcount 1. onClose (may be nil) runs
// when the last reference is released.
func (m *Manager) Create(payload interface{}, onClose func()) *Object {
return &Object{mgr: m, refs: 1, payload: payload, onClose: onClose}
}
// CreateNamed returns the object registered under name, creating it if absent.
// If the name already existed, the existing object is returned with its refcount
// incremented and existed=true (mirrors CreateEvent/Mutex returning
// ERROR_ALREADY_EXISTS). An empty name behaves like Create.
func (m *Manager) CreateNamed(name string, payload interface{}, onClose func()) (obj *Object, existed bool) {
if name == "" {
return m.Create(payload, onClose), false
}
m.mu.Lock()
defer m.mu.Unlock()
if o, ok := m.names[name]; ok {
o.refs++
return o, true
}
o := &Object{mgr: m, name: name, refs: 1, payload: payload, onClose: onClose}
m.names[name] = o
return o, false
}
// Open looks up a named object and increments its refcount, or returns nil.
func (m *Manager) Open(name string) *Object {
m.mu.Lock()
defer m.mu.Unlock()
if o, ok := m.names[name]; ok {
o.refs++
return o
}
return nil
}
// AddRef increments the reference count (NtDuplicateObject / handle inherit).
func (m *Manager) AddRef(o *Object) {
m.mu.Lock()
o.refs++
m.mu.Unlock()
}
// Release decrements the reference count (NtClose). When it reaches zero the
// object is removed from the namespace and onClose is invoked. Returns the
// remaining count.
func (m *Manager) Release(o *Object) int32 {
m.mu.Lock()
o.refs--
n := o.refs
if n <= 0 {
if o.name != "" {
delete(m.names, o.name)
}
}
m.mu.Unlock()
if n <= 0 && o.onClose != nil {
o.onClose()
}
return n
}
// Signal runs fn (which mutates object state under the lock) and then wakes all
// waiters. Object operations (SetEvent, ReleaseSemaphore, ...) funnel through
// here so a single, consistent locking discipline governs every transition.
func (m *Manager) Signal(fn func()) {
m.mu.Lock()
fn()
m.cond.Broadcast()
m.mu.Unlock()
}
// Read runs fn under the manager lock without waking waiters. Use it for
// non-consuming state reads (NtQueryEvent/QuerySemaphore, IsSignaled peeks) that
// must observe object state consistently but never satisfy a wait.
func (m *Manager) Read(fn func()) {
m.mu.Lock()
fn()
m.mu.Unlock()
}
// QueueAPC queues a user-APC for thread tid and wakes alertable waiters. The
// callback runs in the waiting thread when it next enters an alertable wait
// (returned by WaitEx so the caller invokes it outside the manager lock).
func (m *Manager) QueueAPC(tid uint64, fn func()) {
m.mu.Lock()
m.apcs[tid] = append(m.apcs[tid], fn)
m.cond.Broadcast()
m.mu.Unlock()
}
func (m *Manager) DiscardAPCs(tid uint64) int {
m.mu.Lock()
count := len(m.apcs[tid])
delete(m.apcs, tid)
m.mu.Unlock()
return count
}
// Wait is WaitEx without alertability (see WaitEx).
func (m *Manager) Wait(objs []Waitable, waitAll bool, tid uint64, timeout time.Duration) uint32 {
r, _ := m.WaitEx(objs, waitAll, tid, timeout, false)
return r
}
// WaitEx blocks until one (waitAll=false) or all (waitAll=true) of the given
// objects are signaled and atomically satisfies them, until the timeout elapses,
// or - when alertable - until a user-APC is queued for tid. timeout < 0 means
// INFINITE. Returns a Win32 wait code (WaitObject0(+i) / WaitAbandoned0(+i) /
// WaitTimeout / WaitIOCompletion) and, for the WaitIOCompletion case, the drained
// APC callbacks to run outside the lock.
//
// The WaitAll path is atomic: objects are satisfied only when ALL are
// simultaneously signaled, so an auto-reset object is never consumed while the
// thread goes on to block on another - the lost-wakeup bug of sequential waits.
func (m *Manager) WaitEx(objs []Waitable, waitAll bool, tid uint64, timeout time.Duration, alertable bool) (uint32, []func()) {
infinite := timeout < 0
deadline := time.Now().Add(timeout)
// A timer broadcasts at the deadline so the timed-out waiter wakes to return.
var timer *time.Timer
if !infinite {
timer = time.AfterFunc(timeout, func() {
m.mu.Lock()
m.cond.Broadcast()
m.mu.Unlock()
})
defer timer.Stop()
}
m.mu.Lock()
defer m.mu.Unlock()
for {
if waitAll {
all := true
for _, o := range objs {
if !o.signaled(tid) {
all = false
break
}
}
if all {
abandoned := false
for _, o := range objs {
if o.satisfy(tid) {
abandoned = true
}
}
if abandoned {
return WaitAbandoned0, nil
}
return WaitObject0, nil
}
} else {
for i, o := range objs {
if o.signaled(tid) {
ab := o.satisfy(tid)
if ab {
return WaitAbandoned0 + uint32(i), nil
}
return WaitObject0 + uint32(i), nil
}
}
}
// Alertable: a queued user-APC interrupts the wait (WAIT_IO_COMPLETION).
if alertable {
if q := m.apcs[tid]; len(q) > 0 {
callback := q[0]
if len(q) == 1 {
delete(m.apcs, tid)
} else {
m.apcs[tid] = q[1:]
}
return WaitIOCompletion, []func(){callback}
}
}
if !infinite && !time.Now().Before(deadline) {
return WaitTimeout, nil
}
m.cond.Wait()
}
}
// ---------------------------------------------------------------------------
// Concrete waitable object types.
// ---------------------------------------------------------------------------
// Event is a Win32 event. Manual-reset events stay signaled until Reset; an
// auto-reset event releases exactly one waiter and then clears.
type Event struct {
manual bool
state bool
}
// NewEvent creates an event payload.
func NewEvent(manualReset, initial bool) *Event { return &Event{manual: manualReset, state: initial} }
func (e *Event) signaled(uint64) bool { return e.state }
func (e *Event) satisfy(uint64) bool {
if !e.manual {
e.state = false
}
return false
}
// Set/Reset mutate state; call them via Manager.Signal.
func (e *Event) Set() { e.state = true }
func (e *Event) Reset() { e.state = false }
// Peek reports the signaled state without consuming it (manager lock held).
func (e *Event) Peek() bool { return e.state }
// Manual reports whether this is a manual-reset event (manager lock held).
func (e *Event) Manual() bool { return e.manual }
// Timer is a manual-reset or synchronization timer payload. Scheduling lives in
// the handle layer; state transitions run through Manager.Signal.
type Timer struct {
manual bool
state bool
}
func NewTimer(manualReset bool) *Timer { return &Timer{manual: manualReset} }
func (t *Timer) signaled(uint64) bool { return t.state }
func (t *Timer) satisfy(uint64) bool {
if !t.manual {
t.state = false
}
return false
}
func (t *Timer) Set() { t.state = true }
func (t *Timer) Reset() { t.state = false }
func (t *Timer) Peek() bool { return t.state }
func (t *Timer) Manual() bool { return t.manual }
// Semaphore is a counting semaphore with an upper bound.
type Semaphore struct {
count int32
max int32
}
// NewSemaphore creates a semaphore payload.
func NewSemaphore(initial, max int32) *Semaphore { return &Semaphore{count: initial, max: max} }
func (s *Semaphore) signaled(uint64) bool { return s.count > 0 }
func (s *Semaphore) satisfy(uint64) bool { s.count--; return false }
// Release adds n to the count (clamped to max) and returns the previous count
// and whether the addition fit. Call via Manager.Signal.
func (s *Semaphore) Release(n int32) (prev int32, ok bool) {
prev = s.count
if s.count+n > s.max {
return prev, false
}
s.count += n
return prev, true
}
// Count returns the current count (caller must hold the manager lock, e.g. via
// Signal or a test helper).
func (s *Semaphore) Count() int32 { return s.count }
// Max returns the upper bound (manager lock held).
func (s *Semaphore) Max() int32 { return s.max }
// Mutant is a Win32 mutex: recursive ownership by a single thread, with
// abandoned semantics when the owner exits without releasing.
type Mutant struct {
owner uint64 // 0 = unowned
recursion int32
abandoned bool
}
// NewMutant creates a mutant payload, optionally initially owned by tid (0 = not).
func NewMutant(owner uint64) *Mutant {
m := &Mutant{}
if owner != 0 {
m.owner = owner
m.recursion = 1
}
return m
}
func (mu *Mutant) signaled(tid uint64) bool { return mu.owner == 0 || mu.owner == tid }
func (mu *Mutant) satisfy(tid uint64) bool {
if mu.owner == tid {
mu.recursion++
return false
}
// owner == 0: take ownership
mu.owner = tid
mu.recursion = 1
if mu.abandoned {
mu.abandoned = false
return true
}
return false
}
// Release drops one level of ownership; returns false if tid is not the owner.
// Call via Manager.Signal so waiters are woken when ownership is fully dropped.
func (mu *Mutant) Release(tid uint64) bool {
if mu.owner != tid || mu.recursion == 0 {
return false
}
mu.recursion--
if mu.recursion == 0 {
mu.owner = 0
}
return true
}
// Abandon marks the mutant abandoned if tid owned it (owner thread terminated
// without releasing). Call via Manager.Signal.
func (mu *Mutant) Abandon(tid uint64) {
if mu.owner == tid {
mu.owner = 0
mu.recursion = 0
mu.abandoned = true
}
}
// AbandonCurrent marks the mutant abandoned if it is currently owned by anyone
// (used when the owning thread terminates and the tid is taken from the object).
// Call via Manager.Signal.
func (mu *Mutant) AbandonCurrent() {
if mu.owner != 0 {
mu.owner = 0
mu.recursion = 0
mu.abandoned = true
}
}
// Owner returns the current owning tid (0 = unowned).
func (mu *Mutant) Owner() uint64 { return mu.owner }
// Recursion returns the current recursion (lock) count (manager lock held).
func (mu *Mutant) Recursion() int32 { return mu.recursion }
// Latch is a one-shot manual signal used for thread/process termination: once
// Signaled it stays signaled forever and satisfies all current and future
// waiters (NtWaitForSingleObject on a thread/process handle).
type Latch struct{ done bool }
// NewLatch creates an unsignaled latch.
func NewLatch() *Latch { return &Latch{} }
func (l *Latch) signaled(uint64) bool { return l.done }
func (l *Latch) satisfy(uint64) bool { return false }
// Signal marks the latch permanently signaled. Call via Manager.Signal.
func (l *Latch) Signal() { l.done = true }
// IsSignaled reports the latch state (manager lock held).
func (l *Latch) IsSignaled() bool { return l.done }