1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.am;
18 
19 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
20 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
21 import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
22 
23 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
24 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
25 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
26 
27 import android.app.ApplicationExitInfo.Reason;
28 import android.app.ApplicationExitInfo.SubReason;
29 import android.os.Handler;
30 import android.os.Process;
31 import android.os.StrictMode;
32 import android.util.FeatureFlagUtils;
33 import android.util.Slog;
34 import android.util.SparseArray;
35 
36 import com.android.internal.annotations.GuardedBy;
37 import com.android.internal.annotations.VisibleForTesting;
38 import com.android.internal.os.ProcStatsUtil;
39 import com.android.internal.os.ProcessCpuTracker;
40 
41 import libcore.io.IoUtils;
42 
43 import java.io.File;
44 import java.io.FileDescriptor;
45 import java.io.FileInputStream;
46 import java.io.FileNotFoundException;
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.io.PrintWriter;
50 import java.util.ArrayList;
51 import java.util.Collections;
52 import java.util.function.Function;
53 
54 /**
55  * Activity manager code dealing with phantom processes.
56  */
57 public final class PhantomProcessList {
58     static final String TAG = TAG_WITH_CLASS_NAME ? "PhantomProcessList" : TAG_AM;
59 
60     final Object mLock = new Object();
61 
62     /**
63      * All of the phantom process record we track, key is the pid of the process.
64      */
65     @GuardedBy("mLock")
66     final SparseArray<PhantomProcessRecord> mPhantomProcesses = new SparseArray<>();
67 
68     /**
69      * The mapping between app processes and their phantom processess, outer key is the pid of
70      * the app process, while the inner key is the pid of the phantom process.
71      */
72     @GuardedBy("mLock")
73     final SparseArray<SparseArray<PhantomProcessRecord>> mAppPhantomProcessMap =
74             new SparseArray<>();
75 
76     /**
77      * The mapping of the pidfd to PhantomProcessRecord.
78      */
79     @GuardedBy("mLock")
80     final SparseArray<PhantomProcessRecord> mPhantomProcessesPidFds = new SparseArray<>();
81 
82     /**
83      * The list of phantom processes tha's being signaled to be killed but still undead yet.
84      */
85     @GuardedBy("mLock")
86     final SparseArray<PhantomProcessRecord> mZombiePhantomProcesses = new SparseArray<>();
87 
88     @GuardedBy("mLock")
89     private final ArrayList<PhantomProcessRecord> mTempPhantomProcesses = new ArrayList<>();
90 
91     /**
92      * The mapping between a phantom process ID to its parent process (an app process)
93      */
94     @GuardedBy("mLock")
95     private final SparseArray<ProcessRecord> mPhantomToAppProcessMap = new SparseArray<>();
96 
97     @GuardedBy("mLock")
98     private final SparseArray<InputStream> mCgroupProcsFds = new SparseArray<>();
99 
100     @GuardedBy("mLock")
101     private final byte[] mDataBuffer = new byte[4096];
102 
103     @GuardedBy("mLock")
104     private boolean mTrimPhantomProcessScheduled = false;
105 
106     @GuardedBy("mLock")
107     int mUpdateSeq;
108 
109     @VisibleForTesting
110     Injector mInjector;
111 
112     private final ActivityManagerService mService;
113     private final Handler mKillHandler;
114 
115     private static final int CGROUP_V1 = 0;
116     private static final int CGROUP_V2 = 1;
117     private static final String[] CGROUP_PATH_PREFIXES = {
118         "/acct/uid_" /* cgroup v1 */,
119         "/sys/fs/cgroup/uid_" /* cgroup v2 */
120     };
121     private static final String CGROUP_PID_PREFIX = "/pid_";
122     private static final String CGROUP_PROCS = "/cgroup.procs";
123 
124     @VisibleForTesting
125     int mCgroupVersion = CGROUP_V1;
126 
PhantomProcessList(final ActivityManagerService service)127     PhantomProcessList(final ActivityManagerService service) {
128         mService = service;
129         mKillHandler = service.mProcessList.sKillHandler;
130         mInjector = new Injector();
131         probeCgroupVersion();
132     }
133 
134     @VisibleForTesting
135     @GuardedBy("mLock")
lookForPhantomProcessesLocked()136     void lookForPhantomProcessesLocked() {
137         mPhantomToAppProcessMap.clear();
138         StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
139         try {
140             synchronized (mService.mPidsSelfLocked) {
141                 for (int i = mService.mPidsSelfLocked.size() - 1; i >= 0; i--) {
142                     final ProcessRecord app = mService.mPidsSelfLocked.valueAt(i);
143                     lookForPhantomProcessesLocked(app);
144                 }
145             }
146         } finally {
147             StrictMode.setThreadPolicy(oldPolicy);
148         }
149     }
150 
151     @GuardedBy({"mLock", "mService.mPidsSelfLocked"})
lookForPhantomProcessesLocked(ProcessRecord app)152     private void lookForPhantomProcessesLocked(ProcessRecord app) {
153         if (app.appZygote || app.isKilled() || app.isKilledByAm()) {
154             // process forked from app zygote doesn't have its own acct entry
155             return;
156         }
157         final int appPid = app.getPid();
158         InputStream input = mCgroupProcsFds.get(appPid);
159         if (input == null) {
160             final String path = getCgroupFilePath(app.info.uid, appPid);
161             try {
162                 input = mInjector.openCgroupProcs(path);
163             } catch (FileNotFoundException | SecurityException e) {
164                 if (DEBUG_PROCESSES) {
165                     Slog.w(TAG, "Unable to open " + path, e);
166                 }
167                 return;
168             }
169             // Keep the FD open for better performance
170             mCgroupProcsFds.put(appPid, input);
171         }
172         final byte[] buf = mDataBuffer;
173         try {
174             int read = 0;
175             int pid = 0;
176             long totalRead = 0;
177             do {
178                 read = mInjector.readCgroupProcs(input, buf, 0, buf.length);
179                 if (read == -1) {
180                     break;
181                 }
182                 totalRead += read;
183                 for (int i = 0; i < read; i++) {
184                     final byte b = buf[i];
185                     if (b == '\n') {
186                         addChildPidLocked(app, pid, appPid);
187                         pid = 0;
188                     } else {
189                         pid = pid * 10 + (b - '0');
190                     }
191                 }
192                 if (read < buf.length) {
193                     // we may break from here safely as sysfs reading should return the whole page
194                     // if the remaining data is larger than a page
195                     break;
196                 }
197             } while (true);
198             if (pid != 0) {
199                 addChildPidLocked(app, pid, appPid);
200             }
201             // rewind the fd for the next read
202             input.skip(-totalRead);
203         } catch (IOException e) {
204             Slog.e(TAG, "Error in reading cgroup procs from " + app, e);
205             IoUtils.closeQuietly(input);
206             mCgroupProcsFds.delete(appPid);
207         }
208     }
209 
probeCgroupVersion()210     private void probeCgroupVersion() {
211         for (int i = CGROUP_PATH_PREFIXES.length - 1; i >= 0; i--) {
212             if ((new File(CGROUP_PATH_PREFIXES[i] + Process.SYSTEM_UID)).exists()) {
213                 mCgroupVersion = i;
214                 break;
215             }
216         }
217     }
218 
219     @VisibleForTesting
getCgroupFilePath(int uid, int pid)220     String getCgroupFilePath(int uid, int pid) {
221         return CGROUP_PATH_PREFIXES[mCgroupVersion] + uid + CGROUP_PID_PREFIX + pid + CGROUP_PROCS;
222     }
223 
getProcessName(int pid)224     static String getProcessName(int pid) {
225         String procName = ProcStatsUtil.readTerminatedProcFile(
226                 "/proc/" + pid + "/cmdline", (byte) '\0');
227         if (procName == null) {
228             return null;
229         }
230         int l = procName.lastIndexOf('/');
231         if (l > 0 && l < procName.length() - 1) {
232             procName = procName.substring(l + 1);
233         }
234         return procName;
235     }
236 
237     @GuardedBy({"mLock", "mService.mPidsSelfLocked"})
addChildPidLocked(final ProcessRecord app, final int pid, final int appPid)238     private void addChildPidLocked(final ProcessRecord app, final int pid, final int appPid) {
239         if (appPid != pid) {
240             // That's something else...
241             final ProcessRecord r = mService.mPidsSelfLocked.get(pid);
242             if (r != null) {
243                 // Is this a process forked via app zygote?
244                 if (!r.appZygote) {
245                     // Unexpected...
246                     if (DEBUG_PROCESSES) {
247                         Slog.w(TAG, "Unexpected: " + r + " appears in the cgroup.procs of " + app);
248                     }
249                 } else {
250                     // Just a child process of app zygote, no worries
251                 }
252             } else {
253                 final int index = mPhantomToAppProcessMap.indexOfKey(pid);
254                 if (index >= 0) { // unlikely since we cleared the map at the beginning
255                     final ProcessRecord current = mPhantomToAppProcessMap.valueAt(index);
256                     if (app == current) {
257                         // Okay it's unchanged
258                         return;
259                     }
260                     mPhantomToAppProcessMap.setValueAt(index, app);
261                 } else {
262                     mPhantomToAppProcessMap.put(pid, app);
263                 }
264                 // Its UID isn't necessarily to be the same as the app.info.uid, since it could be
265                 // forked from child processes of app zygote
266                 final int uid = Process.getUidForPid(pid);
267                 String procName = mInjector.getProcessName(pid);
268                 if (procName == null || uid < 0) {
269                     mPhantomToAppProcessMap.delete(pid);
270                     return;
271                 }
272                 getOrCreatePhantomProcessIfNeededLocked(procName, uid, pid, true);
273             }
274         }
275     }
276 
onAppDied(final int pid)277     void onAppDied(final int pid) {
278         synchronized (mLock) {
279             final int index = mCgroupProcsFds.indexOfKey(pid);
280             if (index >= 0) {
281                 final InputStream inputStream = mCgroupProcsFds.valueAt(index);
282                 mCgroupProcsFds.removeAt(index);
283                 IoUtils.closeQuietly(inputStream);
284             }
285         }
286     }
287 
288     /**
289      * Get the existing phantom process record, or create if it's not existing yet;
290      * however, before creating it, we'll check if this is really a phantom process
291      * and we'll return null if it's not.
292      */
293     @GuardedBy("mLock")
getOrCreatePhantomProcessIfNeededLocked(final String processName, final int uid, final int pid, boolean createIfNeeded)294     PhantomProcessRecord getOrCreatePhantomProcessIfNeededLocked(final String processName,
295             final int uid, final int pid, boolean createIfNeeded) {
296         // First check if it's actually an app process we know
297         if (isAppProcess(pid)) {
298             return null;
299         }
300 
301         // Have we already been aware of this?
302         final int index = mPhantomProcesses.indexOfKey(pid);
303         if (index >= 0) {
304             final PhantomProcessRecord proc = mPhantomProcesses.valueAt(index);
305             if (proc.equals(processName, uid, pid)) {
306                 return proc;
307             }
308             // Somehow our record doesn't match, remove it anyway
309             Slog.w(TAG, "Stale " + proc + ", removing");
310             onPhantomProcessKilledLocked(proc);
311         } else {
312             // Is this one of the zombie processes we've known?
313             final int idx = mZombiePhantomProcesses.indexOfKey(pid);
314             if (idx >= 0) {
315                 final PhantomProcessRecord proc = mZombiePhantomProcesses.valueAt(idx);
316                 if (proc.equals(processName, uid, pid)) {
317                     return proc;
318                 }
319                 // Our zombie process information is outdated, let's remove this one, it should
320                 // have been gone.
321                 mZombiePhantomProcesses.removeAt(idx);
322             }
323         }
324 
325         if (!createIfNeeded) {
326             return null;
327         }
328 
329         final ProcessRecord r = mPhantomToAppProcessMap.get(pid);
330 
331         if (r != null) {
332             // It's a phantom process, bookkeep it
333             try {
334                 final int appPid = r.getPid();
335                 final PhantomProcessRecord proc = new PhantomProcessRecord(
336                         processName, uid, pid, appPid, mService,
337                         this::onPhantomProcessKilledLocked);
338                 proc.mUpdateSeq = mUpdateSeq;
339                 mPhantomProcesses.put(pid, proc);
340                 SparseArray<PhantomProcessRecord> array = mAppPhantomProcessMap.get(appPid);
341                 if (array == null) {
342                     array = new SparseArray<>();
343                     mAppPhantomProcessMap.put(appPid, array);
344                 }
345                 array.put(pid, proc);
346                 if (proc.mPidFd != null) {
347                     mKillHandler.getLooper().getQueue().addOnFileDescriptorEventListener(
348                             proc.mPidFd, EVENT_INPUT | EVENT_ERROR,
349                             this::onPhantomProcessFdEvent);
350                     mPhantomProcessesPidFds.put(proc.mPidFd.getInt$(), proc);
351                 }
352                 scheduleTrimPhantomProcessesLocked();
353                 return proc;
354             } catch (IllegalStateException e) {
355                 return null;
356             }
357         }
358         return null;
359     }
360 
isAppProcess(int pid)361     private boolean isAppProcess(int pid) {
362         synchronized (mService.mPidsSelfLocked) {
363             return mService.mPidsSelfLocked.get(pid) != null;
364         }
365     }
366 
onPhantomProcessFdEvent(FileDescriptor fd, int events)367     private int onPhantomProcessFdEvent(FileDescriptor fd, int events) {
368         synchronized (mLock) {
369             final PhantomProcessRecord proc = mPhantomProcessesPidFds.get(fd.getInt$());
370             if (proc == null) {
371                 return 0;
372             }
373             if ((events & EVENT_INPUT) != 0) {
374                 proc.onProcDied(true);
375             } else {
376                 // EVENT_ERROR, kill the process
377                 proc.killLocked("Process error", true);
378             }
379         }
380         return 0;
381     }
382 
383     @GuardedBy("mLock")
onPhantomProcessKilledLocked(final PhantomProcessRecord proc)384     private void onPhantomProcessKilledLocked(final PhantomProcessRecord proc) {
385         if (proc.mPidFd != null && proc.mPidFd.valid()) {
386             mKillHandler.getLooper().getQueue()
387                     .removeOnFileDescriptorEventListener(proc.mPidFd);
388             mPhantomProcessesPidFds.remove(proc.mPidFd.getInt$());
389             IoUtils.closeQuietly(proc.mPidFd);
390         }
391         mPhantomProcesses.remove(proc.mPid);
392         final int index = mAppPhantomProcessMap.indexOfKey(proc.mPpid);
393         if (index < 0) {
394             return;
395         }
396         SparseArray<PhantomProcessRecord> array = mAppPhantomProcessMap.valueAt(index);
397         array.remove(proc.mPid);
398         if (array.size() == 0) {
399             mAppPhantomProcessMap.removeAt(index);
400         }
401         if (proc.mZombie) {
402             // If it's not really dead, bookkeep it
403             mZombiePhantomProcesses.put(proc.mPid, proc);
404         } else {
405             // In case of race condition, let's try to remove it from zombie list
406             mZombiePhantomProcesses.remove(proc.mPid);
407         }
408     }
409 
410     @GuardedBy("mLock")
scheduleTrimPhantomProcessesLocked()411     private void scheduleTrimPhantomProcessesLocked() {
412         if (!mTrimPhantomProcessScheduled) {
413             mTrimPhantomProcessScheduled = true;
414             mService.mHandler.post(this::trimPhantomProcessesIfNecessary);
415         }
416     }
417 
418     /**
419      * Clamp the number of phantom processes to
420      * {@link ActivityManagerConstants#MAX_PHANTOM_PROCESSE}, kills those surpluses in the
421      * order of the oom adjs of their parent process.
422      */
trimPhantomProcessesIfNecessary()423     void trimPhantomProcessesIfNecessary() {
424         if (!mService.mSystemReady || !FeatureFlagUtils.isEnabled(mService.mContext,
425                 SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS)) {
426             return;
427         }
428         synchronized (mService.mProcLock) {
429             synchronized (mLock) {
430                 mTrimPhantomProcessScheduled = false;
431                 if (mService.mConstants.MAX_PHANTOM_PROCESSES < mPhantomProcesses.size()) {
432                     for (int i = mPhantomProcesses.size() - 1; i >= 0; i--) {
433                         mTempPhantomProcesses.add(mPhantomProcesses.valueAt(i));
434                     }
435                     synchronized (mService.mPidsSelfLocked) {
436                         Collections.sort(mTempPhantomProcesses, (a, b) -> {
437                             final ProcessRecord ra = mService.mPidsSelfLocked.get(a.mPpid);
438                             if (ra == null) {
439                                 // parent is gone, this process should have been killed too
440                                 return 1;
441                             }
442                             final ProcessRecord rb = mService.mPidsSelfLocked.get(b.mPpid);
443                             if (rb == null) {
444                                 // parent is gone, this process should have been killed too
445                                 return -1;
446                             }
447                             if (ra.mState.getCurAdj() != rb.mState.getCurAdj()) {
448                                 return ra.mState.getCurAdj() - rb.mState.getCurAdj();
449                             }
450                             if (a.mKnownSince != b.mKnownSince) {
451                                 // In case of identical oom adj, younger one first
452                                 return a.mKnownSince < b.mKnownSince ? 1 : -1;
453                             }
454                             return 0;
455                         });
456                     }
457                     for (int i = mTempPhantomProcesses.size() - 1;
458                             i >= mService.mConstants.MAX_PHANTOM_PROCESSES; i--) {
459                         final PhantomProcessRecord proc = mTempPhantomProcesses.get(i);
460                         proc.killLocked("Trimming phantom processes", true);
461                     }
462                     mTempPhantomProcesses.clear();
463                 }
464             }
465         }
466     }
467 
468     /**
469      * Remove all entries with outdated seq num.
470      */
471     @GuardedBy("mLock")
pruneStaleProcessesLocked()472     void pruneStaleProcessesLocked() {
473         for (int i = mPhantomProcesses.size() - 1; i >= 0; i--) {
474             final PhantomProcessRecord proc = mPhantomProcesses.valueAt(i);
475             if (proc.mUpdateSeq < mUpdateSeq) {
476                 if (DEBUG_PROCESSES) {
477                     Slog.v(TAG, "Pruning " + proc + " as it should have been dead.");
478                 }
479                 proc.killLocked("Stale process", true);
480             }
481         }
482         for (int i = mZombiePhantomProcesses.size() - 1; i >= 0; i--) {
483             final PhantomProcessRecord proc = mZombiePhantomProcesses.valueAt(i);
484             if (proc.mUpdateSeq < mUpdateSeq) {
485                 if (DEBUG_PROCESSES) {
486                     Slog.v(TAG, "Pruning " + proc + " as it should have been dead.");
487                 }
488             }
489         }
490     }
491 
492     /**
493      * Kill the given phantom process, all its siblings (if any) and their parent process
494      */
495     @GuardedBy("mService")
killPhantomProcessGroupLocked(ProcessRecord app, PhantomProcessRecord proc, @Reason int reasonCode, @SubReason int subReason, String msg)496     void killPhantomProcessGroupLocked(ProcessRecord app, PhantomProcessRecord proc,
497             @Reason int reasonCode, @SubReason int subReason, String msg) {
498         synchronized (mLock) {
499             int index = mAppPhantomProcessMap.indexOfKey(proc.mPpid);
500             if (index >= 0) {
501                 final SparseArray<PhantomProcessRecord> array =
502                         mAppPhantomProcessMap.valueAt(index);
503                 for (int i = array.size() - 1; i >= 0; i--) {
504                     final PhantomProcessRecord r = array.valueAt(i);
505                     if (r == proc) {
506                         r.killLocked(msg, true);
507                     } else {
508                         r.killLocked("Caused by siling process: " + msg, false);
509                     }
510                 }
511             }
512         }
513         // Lastly, kill the parent process too
514         app.killLocked("Caused by child process: " + msg, reasonCode, subReason, true);
515     }
516 
517     /**
518      * Iterate all phantom process belonging to the given app, and invokve callback
519      * for each of them.
520      */
forEachPhantomProcessOfApp(final ProcessRecord app, final Function<PhantomProcessRecord, Boolean> callback)521     void forEachPhantomProcessOfApp(final ProcessRecord app,
522             final Function<PhantomProcessRecord, Boolean> callback) {
523         synchronized (mLock) {
524             int index = mAppPhantomProcessMap.indexOfKey(app.getPid());
525             if (index >= 0) {
526                 final SparseArray<PhantomProcessRecord> array =
527                         mAppPhantomProcessMap.valueAt(index);
528                 for (int i = array.size() - 1; i >= 0; i--) {
529                     final PhantomProcessRecord r = array.valueAt(i);
530                     if (!callback.apply(r)) {
531                         break;
532                     }
533                 }
534             }
535         }
536     }
537 
538     @GuardedBy("tracker")
updateProcessCpuStatesLocked(ProcessCpuTracker tracker)539     void updateProcessCpuStatesLocked(ProcessCpuTracker tracker) {
540         synchronized (mLock) {
541             // refresh the phantom process list with the latest cpu stats results.
542             mUpdateSeq++;
543 
544             // Scan app process's accounting procs
545             lookForPhantomProcessesLocked();
546 
547             for (int i = tracker.countStats() - 1; i >= 0; i--) {
548                 final ProcessCpuTracker.Stats st = tracker.getStats(i);
549                 final PhantomProcessRecord r =
550                         getOrCreatePhantomProcessIfNeededLocked(st.name, st.uid, st.pid, false);
551                 if (r != null) {
552                     r.mUpdateSeq = mUpdateSeq;
553                     r.mCurrentCputime += st.rel_utime + st.rel_stime;
554                     if (r.mLastCputime == 0) {
555                         r.mLastCputime = r.mCurrentCputime;
556                     }
557                     r.updateAdjLocked();
558                 }
559             }
560             // remove the stale ones
561             pruneStaleProcessesLocked();
562         }
563     }
564 
dump(PrintWriter pw, String prefix)565     void dump(PrintWriter pw, String prefix) {
566         synchronized (mLock) {
567             dumpPhantomeProcessLocked(pw, prefix, "All Active App Child Processes:",
568                     mPhantomProcesses);
569             dumpPhantomeProcessLocked(pw, prefix, "All Zombie App Child Processes:",
570                     mZombiePhantomProcesses);
571         }
572     }
573 
dumpPhantomeProcessLocked(PrintWriter pw, String prefix, String headline, SparseArray<PhantomProcessRecord> list)574     void dumpPhantomeProcessLocked(PrintWriter pw, String prefix, String headline,
575             SparseArray<PhantomProcessRecord> list) {
576         final int size = list.size();
577         if (size == 0) {
578             return;
579         }
580         pw.println();
581         pw.print(prefix);
582         pw.println(headline);
583         for (int i = 0; i < size; i++) {
584             final PhantomProcessRecord proc = list.valueAt(i);
585             pw.print(prefix);
586             pw.print("  proc #");
587             pw.print(i);
588             pw.print(": ");
589             pw.println(proc.toString());
590             proc.dump(pw, prefix + "    ");
591         }
592     }
593 
594     @VisibleForTesting
595     static class Injector {
openCgroupProcs(String path)596         InputStream openCgroupProcs(String path) throws FileNotFoundException, SecurityException {
597             return new FileInputStream(path);
598         }
599 
readCgroupProcs(InputStream input, byte[] buf, int offset, int len)600         int readCgroupProcs(InputStream input, byte[] buf, int offset, int len) throws IOException {
601             return input.read(buf, offset, len);
602         }
603 
getProcessName(final int pid)604         String getProcessName(final int pid) {
605             return PhantomProcessList.getProcessName(pid);
606         }
607     }
608 }
609