By Manny Fernandez

October 14, 2026

What Is That Process Touching? Finding the Files a Running Process Uses on Linux, macOS, and Windows

A practitioner guide to executables, libraries, mapped files, handles, and sockets, with the exact commands for all three operating systems.

Executive Summary

Sooner or later every admin and every incident responder asks the same question: what is this process actually using? Maybe a volume will not unmount because something has it busy. Maybe a log file will not delete. Maybe an EDR alert fired on a binary you have never heard of and you need to know which DLLs it pulled in and which files it has open right now.

All three major operating systems can answer that question, but each one answers it differently, and the default tools on each one hide something important. This guide walks through the built-in and free tooling on Linux, macOS, and Windows, explains how to read what they return, and shows where each tool has blind spots.

Item Detail
Objective Enumerate the executable image, loaded libraries, memory-mapped files, open handles, and sockets for any running process, and do the reverse lookup (which process has this file open).
Target audience Systems engineers, SOC analysts, incident responders, and anyone who has ever stared at a “device or resource busy” error.
Platforms Linux (any modern distro), macOS 13 and later, Windows 10/11 and Server 2019 and later.
Outcome A repeatable workflow plus a cross-platform cheat sheet you can keep open during an investigation.

Prerequisites and Architecture

Assumed Knowledge

  • Comfort with a shell (bash/zsh) and PowerShell.
  • Basic understanding of processes and PIDs.
  • Local admin or sudo rights on the machine you are inspecting. Without them you only see your own processes.

Tooling by Platform

Platform Built in Free add-ons Privilege needed
Linux /proc, ls, readlink, pmap, ss; usually lsof and fuser strace (package manager) root for other users’ processes
macOS lsof, vmmap, fs_usage, Activity Monitor, codesign None required sudo for other users’ processes; SIP still blocks some system processes
Windows PowerShell Get-Process/CIM, tasklist, Resource Monitor Sysinternals: Process Explorer, Handle, ListDLLs, Process Monitor Elevated prompt; protected processes (PPL) stay off limits

Getting Sysinternals

Download the tools from learn.microsoft.com/sysinternals, install the Sysinternals Suite from the Microsoft Store, or run any tool straight from the Sysinternals Live share, for example \\live.sysinternals.com\tools\handle64.exe. Add -accepteula on first run so scripts do not hang on the EULA dialog.

What Does “Using a File” Actually Mean?

When people say a process is “using” a file, they usually mean one of six different things. The operating system tracks each of them separately, which is why one tool rarely shows you the whole picture.

Diagram of the six ways a running process touches files, with the tool that reveals each one on Linux, macOS, and Windows
Figure 1: The six things a process can be “using” and the tool that shows each one.
Category What it is Why it matters
Executable image The binary the process was launched from. First thing to verify in an investigation. Is it where it should be and is it signed?
Shared libraries .so (Linux), .dylib/frameworks (macOS), .dll (Windows) loaded into the address space. DLL sideloading, LD_PRELOAD and dylib hijacking all live here.
Memory-mapped data files Fonts, asset catalogs, databases, caches mapped with mmap or a file mapping object. Explains most of the “why is it touching that?” surprises. Mapped pages are often shared and cost little RAM.
Open file handles Files opened for read or write with a descriptor or handle. Locked files, busy volumes, log files that will not rotate.
Sockets and pipes Network connections, Unix domain sockets, named pipes. C2 callbacks, local IPC, listener ports.
Working directory The directory the process considers “current”. A process sitting in a directory keeps a volume busy even with no files open.

Decoding lsof Output

lsof (list open files) is the common language on Linux and macOS, so it is worth learning its columns once. The FD column is the one that tells you which of the six categories a row belongs to.

Column Meaning
COMMAND / PID / USER Process name (truncated to 9 characters by default), process ID, owning user.
FD File descriptor number or a special label (see next table). A trailing r, w, or u is the access mode: read, write, or both.
TYPE REG regular file, DIR directory, CHR character device, FIFO pipe, IPv4/IPv6 network socket, unix Unix domain socket, KQUEUE (macOS).
DEVICE Major,minor device numbers of the volume the file lives on.
SIZE/OFF File size, or the current read/write offset for some descriptors. It is not memory usage.
NODE Inode number. On macOS, very large values (for example 1152921500312...) indicate the sealed, read-only System volume.
NAME Path, or for sockets the local and remote endpoints.
FD label Linux meaning macOS meaning
cwd Current working directory Current working directory
rtd Root directory (changes under chroot or containers) Rarely shown
txt Program text: the main executable Program text and every memory-mapped file, including dylibs, fonts, and caches
mem Memory-mapped file: shared libraries and mapped data Not used; macOS reports these as txt
DEL Mapped file that has been deleted from disk Not used
0u, 1w, 3r … Numbered descriptor with access mode Numbered descriptor with access mode

The macOS txt surprise

On Linux, -d txt returns one line: the executable. On macOS the same filter returns dozens of lines because macOS labels every mapped file as txt. That is exactly why running lsof -a -d txt -p <PID> against iTerm2 returns fonts, .car asset catalogs, and Metal shader caches alongside the iTerm2 binary. Nothing is wrong; the label simply means something broader on macOS.

Step-by-Step Implementation Workflow

Step 1: Find the PID

Goal: Get the process ID you will feed to every other command.

Action: Search by name, then confirm by looking at the full command line so you do not grab the wrong instance.

Linux and macOS

# Exact name match, print PID and name
pgrep -l nginx

# Linux: show full command line too
pgrep -a nginx

# Both: ps with explicit columns
ps -eo pid,ppid,user,comm | grep -i nginx

Windows (PowerShell)

Get-Process -Name notepad | Select-Object Id, ProcessName, Path

# Classic cmd equivalent
tasklist /fi "imagename eq notepad.exe"

GUI verification: Activity Monitor (macOS) and Task Manager Details tab (Windows) both show a PID column. In Task Manager, right-click the column header and add it if it is hidden.

Step 2: Linux

Linux exposes almost everything through the /proc pseudo-filesystem, so you can answer most questions without installing anything. lsof then wraps it all in one view.

2a. The executable, command line, and working directory

Goal: Identify exactly what binary is running and from where.

Action: Read the exe and cwd symlinks and dump the null-separated command line.

Linux

sudo readlink /proc/<PID>/exe
sudo readlink /proc/<PID>/cwd
sudo tr '\0' ' ' < /proc/<PID>/cmdline; echo

If the binary was deleted after launch, readlink returns the old path with (deleted) appended. The kernel still holds the file, and you can recover it for analysis with sudo cp /proc/<PID>/exe /root/evidence/pid<PID>.bin.

2b. Open file descriptors

Goal: List every file, socket, pipe, and device the process holds open.

Action: List the fd directory. Each entry is a symlink to the real target.

Linux

sudo ls -l /proc/<PID>/fd

# Example output (trimmed)
lrwx------ 1 root root 64 Oct 13 10:02 0 -> /dev/null
l-wx------ 1 root root 64 Oct 13 10:02 2 -> /var/log/nginx/error.log
l-wx------ 1 root root 64 Oct 13 10:02 4 -> /var/log/nginx/access.log
lrwx------ 1 root root 64 Oct 13 10:02 6 -> socket:[48211]

# Offset and open flags for a single descriptor
sudo cat /proc/<PID>/fdinfo/4

A socket:[48211] entry is an inode, not an address. Resolve it to an actual connection with sudo ss -tunap | grep 'pid=<PID>,' (TCP/UDP) or sudo ss -xp | grep 'pid=<PID>,' (Unix sockets).

2c. Shared libraries and memory-mapped files

Goal: See every .so and data file mapped into the address space.

Action: Pull unique file paths from /proc/<PID>/maps, or use pmap for a sized view.

Linux

# Unique mapped files (column 6 is the path)
sudo awk '$6 ~ /^\// {print $6}' /proc/<PID>/maps | sort -u

# Sized view, one line per mapping
sudo pmap -x <PID>

# Mapped files that were deleted or replaced on disk
sudo grep '(deleted)' /proc/<PID>/maps

A library showing (deleted) after a package update is normal and means the process needs a restart to pick up the patched version. A library showing (deleted) from /tmp or /dev/shm is not normal.

2d. Everything at once with lsof

Goal: Get the combined view in one command.

Action: Run lsof against the PID. Use -n -P to skip DNS and port-name lookups, which can make lsof painfully slow.

Linux

# Everything for one PID
sudo lsof -n -P -p <PID>

# Executable plus mapped libraries only
sudo lsof -n -P -a -p <PID> -d txt,mem

# Numbered descriptors only (real open files, sockets, pipes)
sudo lsof -n -P -a -p <PID> -d 0-9999

# Network sockets only
sudo lsof -n -P -a -p <PID> -i

2e. Watch file activity live

Goal: Catch files that are opened and closed too quickly for a snapshot to see.

Action: Attach strace to the PID and filter on file-related system calls. Press Ctrl+C to detach; the process keeps running.

Linux

sudo strace -f -e trace=%file -p <PID>

# Include descriptor-to-path resolution in the output
sudo strace -f -y -e trace=%file,read,write -p <PID>

Step 3: macOS

macOS has no /proc. lsof is the primary tool, but it has one important gap, and vmmap fills it.

3a. lsof on macOS

Goal: List open files and mapped files for the PID.

Action: Same flags as Linux. Remember that on macOS txt covers every mapped file, not just the executable.

macOS

# Everything
sudo lsof -n -P -p <PID>

# Mapped files only (executable, frameworks, fonts, caches)
lsof -a -p <PID> -d txt

# Everything EXCEPT mapped files: real open handles, sockets, cwd
lsof -n -P -a -p <PID> -d '^txt'

# Network only
lsof -n -P -a -p <PID> -i

Here is what the -d txt view looks like against iTerm2, trimmed to the interesting columns:

Example: lsof -a -d txt -p 97070 (trimmed)

FD   SIZE/OFF   NAME
txt  124121456  /Applications/iTerm.app/Contents/MacOS/iTerm2
txt    1123120  /Applications/iTerm.app/.../Sparkle.framework/.../Sparkle
txt    4129840  /usr/lib/dyld
txt     330112  /usr/lib/libobjc-trampolines.dylib
txt    2134132  /System/Library/Fonts/Menlo.ttc
txt    7795064  /System/.../SystemAppearance.bundle/.../DarkAqua.car
txt  192120460  /System/Library/Fonts/Apple Color Emoji.ttc

Notice what is missing: AppKit, Foundation, libSystem, and every other system library. Those are present in memory, but they are not individual files on disk anymore.

3b. Fill the gap: the dyld shared cache and vmmap

Goal: List every loaded library, including the system libraries lsof cannot see.

Action: Since macOS 11, Apple ships system libraries pre-linked inside a single dyld shared cache. lsof only sees the cache, not the libraries inside it. vmmap reads the process memory map and names each image.

macOS

# Every Mach-O image with a __TEXT segment, one per line
vmmap -w <PID> | grep '^__TEXT' | awk '{print $NF}' | sort -u

# Memory summary by region type
vmmap --summary <PID>

Run vmmap with sudo for processes owned by another user. Some Apple platform binaries are protected by SIP and will refuse inspection even from root; that is expected.

3c. GUI view in Activity Monitor

Goal: Get a point-and-click view for a quick check or a screenshot for a ticket.

Action: Double-click the process in Activity Monitor (or select it and press Command+I), then open the Open Files and Ports tab.

GUI verification: the tab lists the same rows as lsof -p <PID>, including the txt mappings and network sockets.

3d. Watch file activity live

Goal: See file system calls in real time.

Action: fs_usage traces file system activity and can filter to a single PID or process name. Press Ctrl+C to stop.

macOS

sudo fs_usage -w -f filesys <PID>

# Filter by name instead of PID
sudo fs_usage -w -f filesys iTerm2

DTrace-based tools such as opensnoop are still installed but are largely blocked while SIP is enabled. fs_usage works without changing SIP.

Step 4: Windows

Windows splits the job across several tools. PowerShell handles the executable and modules, Sysinternals Handle and Process Explorer handle open handles, and Process Monitor handles live activity.

4a. The executable and command line

Goal: Identify the image path, command line, and parent.

Action: Use Get-Process for the path and CIM for the command line and parent PID.

PowerShell (elevated)

(Get-Process -Id <PID>).Path

Get-CimInstance Win32_Process -Filter "ProcessId = <PID>" |
  Select-Object Name, ExecutablePath, CommandLine, ParentProcessId

4b. Loaded DLLs

Goal: List every module loaded into the process.

Action: Use PowerShell for a sortable list, tasklist for a quick built-in view, or ListDLLs when you want unsigned modules flagged.

PowerShell and cmd

# PowerShell: modules with full path and version
(Get-Process -Id <PID>).Modules |
  Select-Object ModuleName, FileName, FileVersion | Sort-Object FileName

# cmd: built in, no install
tasklist /m /fi "PID eq <PID>"

# Sysinternals ListDLLs: all modules, or unsigned modules only
listdlls64.exe -accepteula <PID>
listdlls64.exe -u <PID>

4c. Open handles (files, registry keys, mutexes)

Goal: List the files and other kernel objects the process has open.

Action: Sysinternals Handle is the command-line answer. By default it shows file handles; -a adds every object type.

cmd (elevated)

handle64.exe -accepteula -nobanner -p <PID>

# All handle types: files, keys, sections, mutants, events
handle64.exe -nobanner -a -p <PID>

# Example output (trimmed)
notepad.exe pid: 8120 HOST\manny
   40: File  (RW-)   C:\Windows
  1A4: File  (R-D)   C:\Users\manny\Documents\notes.txt

PowerShell: sockets owned by the PID

Get-NetTCPConnection -OwningProcess <PID>
Get-NetUDPEndpoint -OwningProcess <PID>

4d. GUI views: Process Explorer and Resource Monitor

Goal: Browse handles and DLLs visually.

Action: In Process Explorer, select the process and press Ctrl+L to show the lower pane, then Ctrl+H for handles or Ctrl+D for DLLs. Enable Options > Verify Image Signatures to add a signer column. In Resource Monitor (resmon), open the CPU tab, tick the process, and expand Associated Handles and Associated Modules.

GUI verification: the DLL pane in Process Explorer should match the module list from step 4b. If it shows more, you are probably looking at mapped data files, which Process Explorer lists alongside DLLs.

4e. Watch file activity live

Goal: Capture opens, reads, writes, and image loads as they happen.

Action: Run Process Monitor, open Filter > Filter, and add PID is <PID> then Include. Add Operation is CreateFile for file opens or Operation is Load Image for DLL loads.

Step 5: The Reverse Lookup (Who Has This File Open?)

Goal: Start from a file, folder, DLL, or volume and find the process holding it.

Action: Point the same tools at the path instead of the PID.

Linux and macOS

# Who has this file open
sudo lsof /var/log/syslog

# Everything open under a directory tree (can be slow)
sudo lsof +D /mnt/usb

# Linux: who is keeping a mount busy, with process names
sudo fuser -vm /mnt/usb

Windows

# Which process has a handle to this file (name fragment works)
handle64.exe -nobanner C:\Logs\app.log

# Which processes loaded this DLL
tasklist /m version.dll
listdlls64.exe -d version.dll

GUI verification: Process Explorer’s Find > Find Handle or DLL (Ctrl+F) and the search box under Resource Monitor’s Associated Handles both search across every process at once.

Cross-Platform Cheat Sheet

Question Linux macOS Windows
Executable path readlink /proc/<PID>/exe lsof -a -p <PID> -d txt (first row) or ps -p <PID> -o command= (Get-Process -Id <PID>).Path
Loaded libraries /proc/<PID>/maps, lsof -d mem vmmap -w <PID> .Modules, tasklist /m, listdlls64
Open files ls -l /proc/<PID>/fd, lsof -p lsof -p <PID> -d '^txt' handle64 -p <PID>
Sockets ss -tunap, lsof -i lsof -a -p <PID> -i Get-NetTCPConnection -OwningProcess
Live activity strace -e trace=%file fs_usage -f filesys Process Monitor
Who has file X lsof X, fuser -v X lsof X handle64 X, Resource Monitor
GUI None standard Activity Monitor: Open Files and Ports Process Explorer, Resource Monitor

Verification and Validation

Finding the files is half the job. In an investigation the next question is whether they are legitimate. These checks confirm the image on disk is what it claims to be and flag the classic red flags.

Verify the Executable

Linux: is it owned by a package and unmodified?

dpkg -S "$(sudo readlink /proc/<PID>/exe)"      # Debian/Ubuntu
rpm -qf "$(sudo readlink /proc/<PID>/exe)"       # RHEL/Fedora
dpkg --verify <package>   # or: rpm -V <package>

macOS: is it signed and notarized?

codesign --verify --deep --strict --verbose=2 /Applications/iTerm.app
codesign -dv --verbose=4 /Applications/iTerm.app 2>&1 | grep -E 'Authority|TeamID'
spctl --assess --verbose /Applications/iTerm.app

Windows: is it signed?

Get-AuthenticodeSignature -FilePath (Get-Process -Id <PID>).Path |
  Format-List Status, StatusMessage, SignerCertificate

Expected success output: dpkg -S returns a package name and --verify prints nothing; codesign reports valid on disk and satisfies its Designated Requirement and spctl reports accepted; Get-AuthenticodeSignature returns Status : Valid.

Hunt for Red Flags

Linux: deleted, fileless, or temp-dir binaries

sudo ls -l /proc/*/exe 2>/dev/null |
  grep -E '\(deleted\)|memfd:|/tmp/|/dev/shm/'

Linux and macOS: deleted files still held open

sudo lsof -n -P +L1

Windows: modules loaded from user-writable paths

foreach ($p in Get-Process) {
  try { $mods = $p.Modules } catch { continue }
  $mods | Where-Object { $_.FileName -match '\\(Temp|AppData|ProgramData)\\' } |
    Select-Object @{n='PID';e={$p.Id}}, @{n='Proc';e={$p.ProcessName}}, FileName
}

Expected success output: on a clean server the Linux sweep returns nothing. On a workstation the Windows sweep will return legitimate per-user apps (Teams, Slack, browser updaters); look for the ones you cannot explain, and cross-check them with listdlls64.exe -u.

Troubleshooting and Gotchas

1. Permission Denied or Empty Output

Symptom: /proc/<PID>/fd returns permission denied, lsof shows only a few rows, or .Modules throws an access-denied exception.

Cause: you are not elevated, or the target is protected. On Linux, Yama ptrace_scope can also block strace even as the same user. On macOS, SIP protects many Apple processes from vmmap even as root. On Windows, Protected Process Light (PPL) processes such as LSASS with RunAsPPL or Defender’s MsMpEng.exe refuse module enumeration to administrators.

Diagnose

# Linux: 0 = classic, 1 = parent-only, 2 = admin-only, 3 = disabled
cat /proc/sys/kernel/yama/ptrace_scope

# macOS: is SIP enabled?
csrutil status

Resolution: rerun with sudo or an elevated prompt. For PPL and SIP-protected targets, use handle-level tools (Handle, lsof) which still work, or collect a memory image with a proper forensic tool instead of fighting the protection.

2. macOS Seems to Be Missing Libraries

Symptom: lsof shows app frameworks and dyld but no AppKit, Foundation, or libSystem.

Cause: system libraries live inside the dyld shared cache and have no individual file on disk to report.

Resolution: use vmmap -w <PID> as shown in step 3b. Treat lsof on macOS as “files” and vmmap as “images”.

3. PowerShell Cannot Read Modules of a 64-bit Process

Symptom: A 32 bit processes cannot access modules of a 64 bit process.

Cause: you launched the 32-bit PowerShell (SysWOW64).

Diagnose

[Environment]::Is64BitProcess   # False means you are in 32-bit PowerShell

Resolution: open the 64-bit PowerShell or Windows Terminal, or use listdlls64.exe, which handles both architectures.

4. lsof Is Slow or Hangs

Cause: reverse DNS on every socket, port-name lookups, or a stale network mount.

Resolution: always add -n -P. On Linux, add -b to avoid kernel calls that block on unreachable NFS mounts, and send noise such as can't stat() fuse warnings to 2>/dev/null.

5. Disk Is Full but du Cannot Find the Space

Cause: a file was deleted while a process still had it open (classic with rotated logs). The space is not freed until the last descriptor closes.

Resolution: find it with sudo lsof -n -P +L1, then restart or signal the owning service. In an emergency on Linux you can truncate it through the descriptor: sudo truncate -s 0 /proc/<PID>/fd/<FD>.

6. Snapshot Tools Miss Short-Lived Files

Cause: lsof, Handle, and /proc show a moment in time. A file opened and closed in 20 ms will never appear.

Resolution: switch to a tracing tool: strace on Linux, fs_usage on macOS, Process Monitor on Windows.

7. Paths Do Not Exist When You Look for Them (Containers)

Cause: a containerized process sees its own mount namespace, so /app/config.yml in lsof output is relative to the container, not the host.

Resolution: reach the file through the process root, sudo ls -l /proc/<PID>/root/app/config.yml, or enter its namespaces with sudo nsenter -t <PID> -m -p.

Wrapping Up

The mental model is the same everywhere: an executable, its libraries, mapped data, open handles, sockets, and a working directory. What changes is which tool exposes which piece. On Linux, /proc and lsof see nearly everything. On macOS, pair lsof for files with vmmap for images. On Windows, pair PowerShell for modules with Sysinternals Handle for open files. Keep the cheat sheet handy and you will spend less time hunting for the right command and more time answering the actual question.

InfoSecMonkey: No fluff. Just the config that works.

Recent posts

  • If you've spent any time configuring user authentication on... Full Story

  • DNS is one of those technologies that quietly underpins... Full Story

  • BGP issues on FortiGate firewalls usually trace back to... Full Story

  • Every time your laptop talks to your router, a... Full Story

  • If you've spent any time configuring NAT on a... Full Story

  • If you have spent any time configuring firewall policies... Full Story

  • High availability on FortiGate is one of those features... Full Story

  • If you've configured SD-WAN on a FortiGate, you've almost... Full Story

  • FortiLink is the management protocol that turns a FortiSwitch... Full Story

  • FortiSwitches are pretty rock solid from Mean Time Between... Full Story

  • This is a quicky tip.  Have you ever gone... Full Story

  • DNS is one of those quiet pieces of internet... Full Story

  • This article is an updated version of the previous... Full Story

  • You will add ns2 as a secondary (slave) BIND9... Full Story

  • In the process of deploying my lab, I needed... Full Story

  • RFC 8805, used to be known as Self-Correcting IP... Full Story

  • Years back, I wrote an article about certificate pinning. ... Full Story

  • FortiGates have the ability to send alerts to Microsoft... Full Story

  • In this post, I am going to walk through... Full Story

  • Troubleshooting VoIP on a FortiGate can feel like trying... Full Story

  • Prior to FortiOS 7.0, there were three commands to... Full Story

  • In this post, I am going to go over... Full Story

  • What we are going to do:  We are going... Full Story

  • Choosing between FGCP (FortiGate Clustering Protocol) and FGSP (FortiGate... Full Story

  • Creating a VLAN on macOS (The "Pro" Move) A... Full Story

  • This blog post explores the logic behind how macOS... Full Story

  • Pretty Fly for a Wi-Fi Tell My Wi-Fi Love... Full Story

  • Part of my daily gig is creating BoMs (Bill-of-Materials)... Full Story

  • ICMP introduces several security risks, but careful filtering, rate... Full Story

  • The command diag debug application dhcps -1 enables full... Full Story

  • In the world of FortiOS, execute tac report is... Full Story

  • LLDP; What is it The Link Layer Discovery Protocol... Full Story

  • What it actually does When you run diagnose fdsm... Full Story

  • Monkey Bites are bite-sized, high-impact security insights designed for... Full Story

  • I have run macOS in macOS with Parallels but... Full Story

  • Don't be confused with my other FortiNAC posts where... Full Story

  • This is the third session in a multi-part article... Full Story

  • Today I was configuring key-based authentication on a FortiGate... Full Story

  • Netcat, often called the "Swiss Army knife" of networking,... Full Story

  • At its core, IEEE 802.1X is a network layer... Full Story

  • In case you did not see the previous FortiNAC... Full Story

  • This is our 5th session where we are going... Full Story

  • Now that we have Wireshark installed and somewhat configured,... Full Story

  • The Philosophy of Packet Analysis Troubleshooting isn't about looking... Full Story

  • The Tuesday Afternoon Call It is a quiet Tuesday... Full Story

  • A practitioner breakdown of every TLS version: handshakes, cipher... Full Story

  • A practitioner guide to executables, libraries, mapped files, handles,... Full Story