Everything is an object
On Linux, the unifying abstraction is the file. Processes, devices, sockets, even kernel knobs are reachable as file descriptors and paths under/proc, /sys, and /dev. If I can open, read, and write it, I can reason about it.
Windows does not centre on the file. The kernel’s unifying abstraction is the object, managed by the Object Manager. Processes, threads, files, registry keys, mutexes, tokens — they are all kernel objects of some type, living in an object namespace, and user-mode code rarely touches them directly. Instead it holds a handle: an index into a per-process handle table that points at the real object inside the kernel.
- a file descriptor (Linux) ≈ a
handle(Windows), but the thing on the other end is a typed kernel object, not necessarily a file. - browsing
/proc/<pid>≈ walking objects inWinDbg, which we get to below.
So the first question on Windows is not “what file is this?” but “which object does this handle reference, and what type is it?”.
Processes are containers, threads are what run
Now the objects with the most “wait, that’s different” energy: the things Linux calls tasks. In Windows architecture,tasks is strictly split into processes (containers) and threads (units of execution).
- Process (
nt!_EPROCESS): a static container. It owns the virtual address space, the handle table (open resources), and theAccess Token(its security context). Although its name is process, the thing itself never “runs” :D - Thread (
nt!_ETHREAD): the schedulable thing. It owns the stack, the register context (nt!_KTRAP_FRAME), and scheduling priority. This is the “thing” that executes.
task_struct that blurs the line, where a process is really its main thread and extra threads are just more tasks sharing memory. Windows draws the line hard: the EPROCESS is the box, the ETHREAD is the worker, and the scheduler only ever deals with the worker.
The process owns the address space, but the thread is what the scheduler actually runs. When dumping a process inWinDbg, you don’t look for “the task.” You look for theEPROCESSto find the memory layout, then iterate itsThreadListHeadto find who is actually executing code.
Tokens, address space, and VADs
A few more things hang off theEPROCESS, and each has a familiar echo:
Access Token: the process’s security context — its user, groups, and privileges. This is the credentials side of a Linux process (uid/gid plus capabilities), but bundled as an object the kernel hands around.- Virtual address space: described by
VADs (Virtual Address Descriptors), a tree of what is mapped where. This is the Windows answer to reading/proc/<pid>/maps. - Handle table: the open-resources list, closest to
/proc/<pid>/fd.
So the privilege question (“what can this process do?”) becomes, on Windows, “what is in this process’s Access Token?” — and that token is an object I can inspect directly.
WinDbg has three different faces
WinDbg is not one single experience. Which face we use decides what we can even see.
-
User-mode debugging. This is the equivalent of
gdb ./a.out: one process, its memory, its threads.❌ From here we cannot see
EPROCESS, kernelVADs, tokens, or the scheduler. It is the wrong face for internals. -
Kernel dump analysis. We open a kernel crash dump (
MEMORY.DMP) and explore the kernel’s view offline:EPROCESSandETHREADVADtrees- tokens
- handle tables
- Live kernel analysis. We attach the debugger to a running kernel — the most powerful and the most involved, usually driven from a second machine. A bit of an advanced technique.
The loop is always the same: locate theThat closes the tour. Four phases in, the through-line holds: Linux hands me one abstraction, the file, and Windows hands me another, the object — and most of “learning Windows internals” is retraining that one reflex.EPROCESS, read its address space and token, then iterateThreadListHeadto find theETHREADthat is actually running code. We never schedule a process on Windows; we schedule a thread, and the debugger makes us prove we understand that.