If you open Chrome's own Task Manager it will tell you which tab(s) are using cpu

Either:

Press Shift+Esc

or

Open the three vertical dots "⋮" menu in the top right > More Tool > Task Manager

If Chrome's Task Manager doesn't show the CPU column then right click on one of the column headers and activate it from the menu.


If there aren't any high CPU tasks listed it could be to do with the --field-trial-handle parameter. See this page for how to disable field trials:

https://www.ghacks.net/2013/04/05/field-trials-in-chrome-how-to-randomize-or-force-them/

Specifically, you can start chrome using one of these parameters:

--disable-field-trial-config  —— Disables all field trial tests in fieldtrial_testing_config.json
--fake-variations-channel     —— Fakes the release channel of the browser for testing.
--force-fieldtrials           —— The option can be used to enforce certain field trials in Chrome. Requires knowledge of values.
--force-fieldtrial-params     —— The option can be used to force certain parameters but it is necessary to know valid values to use it.
--force-variation-ids         —— Enforces additional variation IDs.
--reset-variation-state       —— Forces a reset of all Chrome variation states.
--variations-override-country —— Overrides the country used for evaluating variations.
--variations-server-url       —— Specify a custom variation server.
Answer from Sam Hasler on Stack Exchange
🌐
GitHub
github.com › chromelyapps › Chromely › issues › 167
Running in virtual machine supported? · Issue #167 · chromelyapps/Chromely
March 7, 2020 - pi@pi-VirtualBox:~/Downloads/linux-x64$ ./crossplatdemo 2020-03-07 15:54:52.235 [INFO] Running Linux chromium 77.0.3865.120 On CefGlue child process launch arguments: /proc/self/exe --type=gpu-process --field-trial-handle=8883176290764582883,17956091608203660837,131072 --disable-features=OutOfBlinkCors --no-sandbox --locales-dir-path=/home/pi/Downloads/linux-x64/locales --log-file=logs\chromely.cef_20200307.log --log-severity=info --resources-dir-path=/home/pi/Downloads/linux-x64/ --lang=en-US --default-encoding=utf-8 --allow-file-access-from-files --allow-universal-access-from-files --disable
Author   chromelyapps
Top answer
1 of 2
4

If you open Chrome's own Task Manager it will tell you which tab(s) are using cpu

Either:

Press Shift+Esc

or

Open the three vertical dots "⋮" menu in the top right > More Tool > Task Manager

If Chrome's Task Manager doesn't show the CPU column then right click on one of the column headers and activate it from the menu.


If there aren't any high CPU tasks listed it could be to do with the --field-trial-handle parameter. See this page for how to disable field trials:

https://www.ghacks.net/2013/04/05/field-trials-in-chrome-how-to-randomize-or-force-them/

Specifically, you can start chrome using one of these parameters:

--disable-field-trial-config  —— Disables all field trial tests in fieldtrial_testing_config.json
--fake-variations-channel     —— Fakes the release channel of the browser for testing.
--force-fieldtrials           —— The option can be used to enforce certain field trials in Chrome. Requires knowledge of values.
--force-fieldtrial-params     —— The option can be used to force certain parameters but it is necessary to know valid values to use it.
--force-variation-ids         —— Enforces additional variation IDs.
--reset-variation-state       —— Forces a reset of all Chrome variation states.
--variations-override-country —— Overrides the country used for evaluating variations.
--variations-server-url       —— Specify a custom variation server.
2 of 2
4

Epilogue: It was indeed a crypto-miner hidden inside of an headless chrome.

For anyone that has the same problem, the file was located in the following directory : C:\Windows\System32\config\systemprofile\AppData\Local\Google\Chrome\User Data\Default\Cache\f_00002d

Only Kaspersky detects the virus at the moment. Here's the VirusTotal link.

Deleting the file solved the problem.

🌐
Reddit
reddit.com › r/chrome › chrome background process high cpu usage
r/chrome on Reddit: Chrome Background Process High CPU Usage
July 1, 2020 -

There is a Chrome background process that always consumes most of my CPU resources(80-90%), it can easily kill the process and it doesn't come back, but it's weird that Chrome does this. I am suspicious that my PC got a virus. Any ideasof what this is? Its command line from the task manager is:

"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --type=renderer --allow-pre-commit-input --remote-debugging-port=9222 --field-trial-handle=1312,5801044152304138052,10048629155968864358,131072 --disable-databases --disable-gpu-compositing --lang=en-US --headless --lang=en-US --device-scale-factor=1.25 --num-raster-threads=4 --enable-main-frame-before-activation --renderer-client-id=4 --mojo-platform-channel-handle=1828 /prefetch:1"

Edit: Got rid of the thing using MalwareBytes. Looks like it was some kind of cryptominer: https://support.google.com/chrome/thread/24386432?hl=en

🌐
GitHub
github.com › leclercb › taskunifier-app › issues › 176
taskunifier2 monopolizes the CPU somehow · Issue #176 · leclercb/taskunifier-app
March 15, 2020 - %CPU %MEM CMD 5.0 1.2 /opt/new_taskunifier/taskunifier-app 0.0 0.2 /opt/new_taskunifier/taskunifier-app --type=zygote 0.0 0.0 /opt/new_taskunifier/taskunifier-app --type=zygote 19.9 0.5 /opt/new_taskunifier/taskunifier-app --type=gpu-process --field-trial- 0.0 0.3 /opt/new_taskunifier/taskunifier-app --type=utility --field-trial-hand 76.1 1.0 /proc/self/exe --type=renderer --field-trial-handle=178690833322806740
Author   leclercb
🌐
gHacks Tech News
ghacks.net › home › google chrome › field trials in chrome, how to randomize or force them
Field Trials in Chrome, how to randomize or force them - gHacks Tech News
February 18, 2019 - On Debian Buster I had to kill this process that was hogging the CPU: /proc/self/exe · –type=renderer –no-sandbox –file-url-path-alias=/gen=/opt/OnlyKey/gen –no-zygote –field-trial-handle=15862159829197978252,11898307318573346910,131072 –lang=en-US –enable-crash-reporter=, –user-data-dir=/home/howard/.config/OnlyKey –nwapp-path=/opt/OnlyKey –nwjs –extension-process –num-raster-threads=1 –renderer-client-id=5 –shared-files ·
Find elsewhere
Top answer
1 of 1
1

This really isn't an answer of why Chrome launches so many processes but more of an answer to show that Chrome in general uses more resources than other browsers. I am comparing Firefox with it as an example.

Web browsers use both CPU and RAM when they launch. Unfortunately, you are not only launching the web browser, but also launch the plug-ins, etc. Also, a lot of it is up to the programmers of the web browsers in how they actually run in the system and what they feel needs to be running at the same time.

The following examples of using inxi (one of my favorite tools to see usage on a system) to see both CPU and RAM usage of Chrome and Firefox compared to each other.

From the examples of the top 1000 CPU and RAM listings from inxi and both browsers having 1 tab open. From these examples we can see that Firefox uses a bit more of the overall CPU usage than Chrome does. But, we can also see that Chrome uses more of the overall used RAM. A lot of this really boils down to preference of what browser you want to use.

CPU usage:

terrance@terrance-ubuntu:~$ inxi -t c1000 | grep -E "firefox|chrome"
  1: cpu: 17.3% command: firefox pid: 3910531 
  3: cpu: 9.5% command: firefox pid: 3910586 
  4: cpu: 6.2% command: firefox pid: 3910656 
  6: cpu: 4.7% command: chrome pid: 3900194 
  8: cpu: 2.0% command: firefox pid: 3910707 
  9: cpu: 1.3% command: chrome pid: 3900266 
  11: cpu: 1.0% command: chrome pid: 3900302 
  13: cpu: 0.9% command: chrome pid: 3900235 
  14: cpu: 0.8% command: chrome pid: 3900233 
  15: cpu: 0.8% command: firefox pid: 3910735 
  17: cpu: 0.5% command: chrome-gnome-shell started by: python3 pid: 3910759 
  20: cpu: 0.2% command: chrome pid: 3900333 
  21: cpu: 0.2% command: chrome pid: 3900339 
  24: cpu: 0.1% command: chrome pid: 3900369 
  25: cpu: 0.1% command: chrome-gnome-shell started by: python3 pid: 3900463 
  26: cpu: 0.1% command: chrome-gnome-shell started by: python3 pid: 3900470 
  326: cpu: 0.0% command: chrome pid: 3900204 
  327: cpu: 0.0% command: chrome pid: 3900205 
  329: cpu: 0.0% command: chrome pid: 3900209 
  330: cpu: 0.0% command: chrome pid: 3900240 
  331: cpu: 0.0% command: chrome pid: 3900257 
  332: cpu: 0.0% command: chrome pid: 3900316 
  333: cpu: 0.0% command: chrome pid: 3900321 
  334: cpu: 0.0% command: chrome pid: 3900349 
  335: cpu: 0.0% command: chrome pid: 3900426 
  336: cpu: 0.0% command: chrome pid: 3900449 
  337: cpu: 0.0% command: chrome pid: 3900478 

RAM usage:

terrance@terrance-ubuntu:~$ inxi -t m1000 | grep -E "firefox|chrome"
  1: mem: 330.1 MiB (1.3%) command: firefox pid: 3910531 
  2: mem: 230.6 MiB (0.9%) command: firefox pid: 3910586 
  3: mem: 222.3 MiB (0.9%) command: firefox pid: 3910656 
  4: mem: 198.4 MiB (0.8%) command: chrome pid: 3900194 
  5: mem: 149.2 MiB (0.6%) command: chrome pid: 3900233 
  6: mem: 137.7 MiB (0.5%) command: firefox pid: 3910707 
  7: mem: 124.8 MiB (0.5%) command: chrome pid: 3900266 
  9: mem: 111.6 MiB (0.4%) command: chrome pid: 3900302 
  10: mem: 94.5 MiB (0.3%) command: chrome pid: 3900339 
  11: mem: 94.3 MiB (0.3%) command: chrome pid: 3900333 
  12: mem: 86.3 MiB (0.3%) command: chrome pid: 3900369 
  13: mem: 85.5 MiB (0.3%) command: chrome pid: 3900449 
  14: mem: 83.9 MiB (0.3%) command: chrome pid: 3900321 
  15: mem: 83.7 MiB (0.3%) command: firefox pid: 3910735 
  18: mem: 80.2 MiB (0.3%) command: chrome pid: 3900316 
  19: mem: 79.5 MiB (0.3%) command: chrome pid: 3900235 
  21: mem: 75.6 MiB (0.3%) command: chrome pid: 3900349 
  30: mem: 56.0 MiB (0.2%) command: chrome pid: 3900478 
  33: mem: 53.4 MiB (0.2%) command: chrome pid: 3900204 
  34: mem: 52.9 MiB (0.2%) command: chrome pid: 3900205 
  35: mem: 51.0 MiB (0.2%) command: chrome pid: 3900426 
  52: mem: 34.9 MiB (0.1%) command: chrome-gnome-shell started by: python3 
  53: mem: 34.8 MiB (0.1%) command: chrome-gnome-shell started by: python3 
  54: mem: 34.5 MiB (0.1%) command: chrome-gnome-shell started by: python3 
  56: mem: 31.9 MiB (0.1%) command: chrome pid: 3900240 
  58: mem: 31.1 MiB (0.1%) command: chrome pid: 3900257 
  82: mem: 14.9 MiB (0.0%) command: chrome pid: 3900209 

Also to note that each tab you open uses more RAM as well, but very little extra CPU.

Firefox with 1 tab:

terrance@terrance-ubuntu:~$ inxi -t m1000 | grep firefox
  1: mem: 394.1 MiB (1.6%) command: firefox pid: 3910531 
  2: mem: 351.5 MiB (1.4%) command: firefox pid: 3910586 
  3: mem: 175.7 MiB (0.7%) command: firefox pid: 3910656 
  4: mem: 137.3 MiB (0.5%) command: firefox pid: 3910707 
  6: mem: 83.9 MiB (0.3%) command: firefox pid: 3910735 

Firefox with 3 tabs:

terrance@terrance-ubuntu:~$ inxi -t m1000 | grep firefox
  1: mem: 354.3 MiB (1.4%) command: firefox pid: 3910531 
  2: mem: 313.5 MiB (1.3%) command: firefox pid: 3910586 
  3: mem: 177.1 MiB (0.7%) command: firefox pid: 3910656 
  4: mem: 170.7 MiB (0.7%) command: firefox pid: 3910707 
  6: mem: 83.9 MiB (0.3%) command: firefox pid: 3910735 

Firefox with 5 tabs:

terrance@terrance-ubuntu:~$ inxi -t m1000 | grep firefox
  1: mem: 358.6 MiB (1.4%) command: firefox pid: 3910531 
  2: mem: 313.2 MiB (1.3%) command: firefox pid: 3910586 
  3: mem: 177.7 MiB (0.7%) command: firefox pid: 3910656 
  4: mem: 177.7 MiB (0.7%) command: firefox pid: 3910707 
  6: mem: 83.9 MiB (0.3%) command: firefox pid: 3910735 

Chrome with 1 tab:

terrance@terrance-ubuntu:~$ inxi -t m1000 | grep chrome
  3: mem: 201.3 MiB (0.8%) command: chrome pid: 4007777 
  6: mem: 145.7 MiB (0.6%) command: chrome pid: 4007815 
  7: mem: 122.3 MiB (0.5%) command: chrome pid: 4007853 
  9: mem: 111.1 MiB (0.4%) command: chrome pid: 4007889 
  10: mem: 92.7 MiB (0.3%) command: chrome pid: 4007920 
  11: mem: 91.8 MiB (0.3%) command: chrome pid: 4007923 
  12: mem: 85.4 MiB (0.3%) command: chrome pid: 4007981 
  13: mem: 85.0 MiB (0.3%) command: chrome pid: 4007962 
  15: mem: 82.5 MiB (0.3%) command: chrome pid: 4007916 
  18: mem: 79.2 MiB (0.3%) command: chrome pid: 4007903 
  20: mem: 76.2 MiB (0.3%) command: chrome pid: 4007817 
  21: mem: 74.5 MiB (0.3%) command: chrome pid: 4007925 
  30: mem: 56.2 MiB (0.2%) command: chrome pid: 4008026 
  33: mem: 53.6 MiB (0.2%) command: chrome pid: 4007786 
  34: mem: 53.5 MiB (0.2%) command: chrome pid: 4007787 
  35: mem: 51.9 MiB (0.2%) command: chrome pid: 4008005 
  52: mem: 34.8 MiB (0.1%) command: chrome-gnome-shell started by: python3 
  54: mem: 32.1 MiB (0.1%) command: chrome pid: 4007822 
  56: mem: 31.0 MiB (0.1%) command: chrome pid: 4007844 
  80: mem: 14.9 MiB (0.0%) command: chrome pid: 4007791 

Chrome with 3 tabs:

terrance@terrance-ubuntu:~$ inxi -t m1000 | grep chrome
  3: mem: 205.7 MiB (0.8%) command: chrome pid: 4007777 
  6: mem: 156.8 MiB (0.6%) command: chrome pid: 4007815 
  7: mem: 126.5 MiB (0.5%) command: chrome pid: 4007853 
  8: mem: 115.2 MiB (0.4%) command: chrome pid: 4008005 
  10: mem: 110.9 MiB (0.4%) command: chrome pid: 4007889 
  11: mem: 100.4 MiB (0.4%) command: chrome pid: 4012062 
  12: mem: 92.7 MiB (0.3%) command: chrome pid: 4007923 
  13: mem: 92.5 MiB (0.3%) command: chrome pid: 4007920 
  14: mem: 85.2 MiB (0.3%) command: chrome pid: 4007962 
  16: mem: 82.5 MiB (0.3%) command: chrome pid: 4007916 
  19: mem: 79.2 MiB (0.3%) command: chrome pid: 4007903 
  20: mem: 76.7 MiB (0.3%) command: chrome pid: 4007817 
  22: mem: 74.6 MiB (0.3%) command: chrome pid: 4007925 
  31: mem: 56.3 MiB (0.2%) command: chrome pid: 4008026 
  34: mem: 53.6 MiB (0.2%) command: chrome pid: 4007786 
  35: mem: 53.5 MiB (0.2%) command: chrome pid: 4007787 
  36: mem: 51.0 MiB (0.2%) command: chrome pid: 4012075 
  53: mem: 34.8 MiB (0.1%) command: chrome-gnome-shell started by: python3 
  55: mem: 32.2 MiB (0.1%) command: chrome pid: 4007822 
  57: mem: 31.0 MiB (0.1%) command: chrome pid: 4007844 
  81: mem: 15.0 MiB (0.0%) command: chrome pid: 4007791 

Chrome with 5 tabs:

terrance@terrance-ubuntu:~$ inxi -t m1000 | grep chrome
  3: mem: 211.4 MiB (0.8%) command: chrome pid: 4007777 
  5: mem: 158.7 MiB (0.6%) command: chrome pid: 4007815 
  7: mem: 131.2 MiB (0.5%) command: chrome pid: 4007853 
  8: mem: 129.3 MiB (0.5%) command: chrome pid: 4008005 
  10: mem: 109.5 MiB (0.4%) command: chrome pid: 4007889 
  11: mem: 105.8 MiB (0.4%) command: chrome pid: 4012062 
  12: mem: 92.9 MiB (0.3%) command: chrome pid: 4007923 
  13: mem: 92.8 MiB (0.3%) command: chrome pid: 4007920 
  14: mem: 85.2 MiB (0.3%) command: chrome pid: 4007962 
  16: mem: 82.6 MiB (0.3%) command: chrome pid: 4007916 
  19: mem: 79.4 MiB (0.3%) command: chrome pid: 4007903 
  20: mem: 77.9 MiB (0.3%) command: chrome pid: 4007817 
  22: mem: 74.6 MiB (0.3%) command: chrome pid: 4007925 
  31: mem: 56.3 MiB (0.2%) command: chrome pid: 4008026 
  34: mem: 53.6 MiB (0.2%) command: chrome pid: 4007786 
  35: mem: 53.5 MiB (0.2%) command: chrome pid: 4007787 
  36: mem: 51.2 MiB (0.2%) command: chrome pid: 4012075 
  53: mem: 34.8 MiB (0.1%) command: chrome-gnome-shell started by: python3 
  55: mem: 32.2 MiB (0.1%) command: chrome pid: 4007822 
  57: mem: 31.0 MiB (0.1%) command: chrome pid: 4007844 
  81: mem: 15.0 MiB (0.0%) command: chrome pid: 4007791 
🌐
GNU
gnu.org › software › hurd › hurd › translator › procfs › jkoenig › discussion.html
discussion
October 3, 2013 - <youpi> no, only argv[0] <mjt> busybox uses /proc/self/exe by default to re-exec itself when running one of its applets, or failing that, tries to find it in $PATH. I guess it doesn't work on hurd...
🌐
Super User
superuser.com › questions › 1539288 › understanding-the-gpu-processes-running-on-my-system
xorg - Understanding the GPU processes running on my system - Super User
Also the ... ellipsis means it's only showing the last part of the command used to execute the process. Finally, you can use that last part of the command to find the full command by using ps aux | grep 'gAAAAAAAAA' as the process should show up there too. ... Thanks for the suggestions, running the command gave me the output /opt/google/chrome/chrome --type=gpu-process --field-trial-handle.....
🌐
Microsoft Learn
learn.microsoft.com › en-us › answers › questions › 924203 › how-to-handle-multi-stage-incident-involving-execu
How to handle Multi-stage incident involving Execution & Discovery? - Microsoft Q&A
July 12, 2022 - • "Healthy.exe" --type=utility --utility-sub-type=data_decoder.mojom.DataDecoderService --field-trial-handle=1672,13707566958700461977,3626767176628270657,131072 --lang=en-US --service-sandbox-type=utility --no-sandbox --enable-audio-service-sandbox --user-data-dir="C:\Users\DG\AppData\Local\Healthy\User Data" --nwapp-path="C:\Users\DG~1\AppData\Local\Temp\nw12476_1097854171" --mojo-platform-channel-handle=3828 /prefetch:8 · • "Healthy.exe" --type=gpu-process --field-trial-handle=1672,13707566958700461977,3626767176628270657,131072 --disable-gpu-sandbox --use-gl=disabled --gpu-vendor-id=3
Top answer
1 of 16
398

Some OS-specific interfaces:

  • Mac OS X: _NSGetExecutablePath() (man 3 dyld)
  • Linux: readlink /proc/self/exe
  • Solaris: getexecname()
  • FreeBSD: sysctl CTL_KERN KERN_PROC KERN_PROC_PATHNAME -1
  • FreeBSD if it has procfs: readlink /proc/curproc/file (FreeBSD doesn't have procfs by default)
  • NetBSD: readlink /proc/curproc/exe
  • DragonFly BSD: readlink /proc/curproc/file
  • Windows: GetModuleFileName() with hModule = NULL

There are also third party libraries that can be used to get this information, such as whereami as mentioned in prideout's answer, or if you are using Qt, QCoreApplication::applicationFilePath() as mentioned in the comments.

The portable (but less reliable) method is to use argv[0]. Although it could be set to anything by the calling program, by convention it is set to either a path name of the executable or a name that was found using $PATH.

Some shells, including bash and ksh, set the environment variable "_" to the full path of the executable before it is executed. In that case you can use getenv("_") to get it. However this is unreliable because not all shells do this, and it could be set to anything or be left over from a parent process which did not change it before executing your program.

2 of 16
45

The use of /proc/self/exe is non-portable and unreliable. On my Ubuntu 12.04 system, you must be root to read/follow the symlink. This will make the Boost example and probably the whereami() solutions posted fail.

This post is very long but discusses the actual issues and presents code which actually works along with validation against a test suite.

The best way to find your program is to retrace the same steps the system uses. This is done by using argv[0] resolved against file system root, pwd, path environment and considering symlinks, and pathname canonicalization. This is from memory but I have done this in the past successfully and tested it in a variety of different situations. It is not guaranteed to work, but if it doesn't you probably have much bigger problems and it is more reliable overall than any of the other methods discussed. There are situations on a Unix compatible system in which proper handling of argv[0] will not get you to your program but then you are executing in a certifiably broken environment. It is also fairly portable to all Unix derived systems since around 1970 and even some non-Unix derived systems as it basically relies on libc() standard functionality and standard command line functionality. It should work on Linux (all versions), Android, Chrome OS, Minix, original Bell Labs Unix, FreeBSD, NetBSD, OpenBSD, BSD x.x, SunOS, Solaris, SYSV, HP-UX, Concentrix, SCO, Darwin, AIX, OS X, NeXTSTEP, etc. And with a little modification probably VMS, VM/CMS, DOS/Windows, ReactOS, OS/2, etc. If a program was launched directly from a GUI environment, it should have set argv[0] to an absolute path.

Understand that almost every shell on every Unix compatible operating system that has ever been released basically finds programs the same way and sets up the operating environment almost the same way (with some optional extras). And any other program that launches a program is expected to create the same environment (argv, environment strings, etc.) for that program as if it were run from a shell, with some optional extras. A program or user can setup an environment that deviates from this convention for other subordinate programs that it launches but if it does, this is a bug and the program has no reasonable expectation that the subordinate program or its subordinates will function correctly.

Possible values of argv[0] include:

  • /path/to/executable — absolute path
  • ../bin/executable — relative to pwd
  • bin/executable — relative to pwd
  • ./foo — relative to pwd
  • executable — basename, find in path
  • bin//executable — relative to pwd, non-canonical
  • src/../bin/executable — relative to pwd, non-canonical, backtracking
  • bin/./echoargc — relative to pwd, non-canonical

Values you should not see:

  • ~/bin/executable — rewritten before your program runs.
  • ~user/bin/executable — rewritten before your program runs
  • alias — rewritten before your program runs
  • $shellvariable — rewritten before your program runs
  • *foo* — wildcard, rewritten before your program runs, not very useful
  • ?foo? — wildcard, rewritten before your program runs, not very useful

In addition, these may contain non-canonical path names and multiple layers of symbolic links. In some cases, there may be multiple hard links to the same program. For example, /bin/ls, /bin/ps, /bin/chmod, /bin/rm, etc. may be hard links to /bin/busybox.

To find yourself, follow the steps below:

  • Save pwd, PATH, and argv[0] on entry to your program (or initialization of your library) as they may change later.

  • Optional: particularly for non-Unix systems, separate out but don't discard the pathname host/user/drive prefix part, if present; the part which often precedes a colon or follows an initial "//".

  • If argv[0] is an absolute path, use that as a starting point. An absolute path probably starts with "/" but on some non-Unix systems it might start with "" or a drive letter or name prefix followed by a colon.

  • Else if argv[0] is a relative path (contains "/" or "" but doesn't start with it, such as "../../bin/foo", then combine pwd+"/"+argv[0] (use present working directory from when program started, not current).

  • Else if argv[0] is a plain basename (no slashes), then combine it with each entry in PATH environment variable in turn and try those and use the first one which succeeds.

  • Optional: Else try the very platform specific /proc/self/exe, /proc/curproc/file (BSD), and (char *)getauxval(AT_EXECFN), and dlgetname(...) if present. You might even try these before argv[0]-based methods, if they are available and you don't encounter permission issues. In the somewhat unlikely event (when you consider all versions of all systems) that they are present and don't fail, they might be more authoritative.

  • Optional: check for a path name passed in using a command line parameter.

  • Optional: check for a pathname in the environment explicitly passed in by your wrapper script, if any.

  • Optional: As a last resort try environment variable "_". It might point to a different program entirely, such as the users shell.

  • Resolve symlinks, there may be multiple layers. There is the possibility of infinite loops, though if they exist your program probably won't get invoked.

  • Canonicalize filename by resolving substrings like "/foo/../bar/" to "/bar/". Note this may potentially change the meaning if you cross a network mount point, so canonization is not always a good thing. On a network server, ".." in symlink may be used to traverse a path to another file in the server context instead of on the client. In this case, you probably want the client context so canonicalization is ok. Also convert patterns like "/./" to "/" and "//" to "/". In shell, readlink --canonicalize will resolve multiple symlinks and canonicalize name. Chase may do similar but isn't installed. realpath() or canonicalize_file_name(), if present, may help.

If realpath() doesn't exist at compile time, you might borrow a copy from a permissively licensed library distribution, and compile it in yourself rather than reinventing the wheel. Fix the potential buffer overflow (pass in sizeof output buffer, think strncpy() vs strcpy()) if you will be using a buffer less than PATH_MAX. It may be easier just to use a renamed private copy rather than testing if it exists. Permissive license copy from android/darwin/bsd: https://android.googlesource.com/platform/bionic/+/f077784/libc/upstream-freebsd/lib/libc/stdlib/realpath.c

Be aware that multiple attempts may be successful or partially successful and they might not all point to the same executable, so consider verifying your executable; however, you may not have read permission — if you can't read it, don't treat that as a failure. Or verify something in proximity to your executable such as the "../lib/" directory you are trying to find. You may have multiple versions, packaged and locally compiled versions, local and network versions, and local and USB-drive portable versions, etc. and there is a small possibility that you might get two incompatible results from different methods of locating. And "_" may simply point to the wrong program.

A program using execve can deliberately set argv[0] to be incompatible with the actual path used to load the program and corrupt PATH, "_", pwd, etc. though there isn't generally much reason to do so; but this could have security implications if you have vulnerable code that ignores the fact that your execution environment can be changed in variety of ways including, but not limited, to this one (chroot, fuse filesystem, hard links, etc.) It is possible for shell commands to set PATH but fail to export it.

You don't necessarily need to code for non-Unix systems but it would be a good idea to be aware of some of the peculiarities so you can write the code in such a way that it isn't as hard for someone to port later. Be aware that some systems (DEC VMS, DOS, URLs, etc.) might have drive names or other prefixes which end with a colon such as "C:", "sys$drive:[foo]bar", and "file:///foo/bar/baz". Old DEC VMS systems use "[" and "]" to enclose the directory portion of the path though this may have changed if your program is compiled in a POSIX environment. Some systems, such as VMS, may have a file version (separated by a semicolon at the end). Some systems use two consecutive slashes as in "//drive/path/to/file" or "user@host:/path/to/file" (scp command) or "file://hostname/path/to/file" (URL). In some cases (DOS and Windows), PATH might have different separator characters — ";" vs ":" and "" vs "/" for a path separator. In csh/tsh there is "path" (delimited with spaces) and "PATH" delimited with colons but your program should receive PATH so you don't need to worry about path. DOS and some other systems can have relative paths that start with a drive prefix. C:foo.exe refers to foo.exe in the current directory on drive C, so you do need to lookup current directory on C: and use that for pwd.

An example of symlinks and wrappers on my system:

/usr/bin/google-chrome is symlink to
/etc/alternatives/google-chrome  which is symlink to
/usr/bin/google-chrome-stable which is symlink to
/opt/google/chrome/google-chrome which is a bash script which runs
/opt/google/chome/chrome

Note that user bill posted a link above to a program at HP that handles the three basic cases of argv[0]. It needs some changes, though:

  • It will be necessary to rewrite all the strcat() and strcpy() to use strncat() and strncpy(). Even though the variables are declared of length PATHMAX, an input value of length PATHMAX-1 plus the length of concatenated strings is > PATHMAX and an input value of length PATHMAX would be unterminated.
  • It needs to be rewritten as a library function, rather than just to print out results.
  • It fails to canonicalize names (use the realpath code I linked to above)
  • It fails to resolve symbolic links (use the realpath code)

So, if you combine both the HP code and the realpath code and fix both to be resistant to buffer overflows, then you should have something which can properly interpret argv[0].

The following illustrates actual values of argv[0] for various ways of invoking the same program on Ubuntu 12.04. And yes, the program was accidentally named echoargc instead of echoargv. This was done using a script for clean copying but doing it manually in shell gets same results (except aliases don't work in script unless you explicitly enable them).

cat ~/src/echoargc.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
main(int argc, char **argv)
{
  printf("  argv[0]=\"%s\"\n", argv[0]);
  sleep(1);  /* in case run from desktop */
}
tcc -o ~/bin/echoargc ~/src/echoargc.c
cd ~
/home/whitis/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
echoargc
  argv[0]="echoargc"
bin/echoargc
  argv[0]="bin/echoargc"
bin//echoargc
  argv[0]="bin//echoargc"
bin/./echoargc
  argv[0]="bin/./echoargc"
src/../bin/echoargc
  argv[0]="src/../bin/echoargc"
cd ~/bin
*echo*
  argv[0]="echoargc"
e?hoargc
  argv[0]="echoargc"
./echoargc
  argv[0]="./echoargc"
cd ~/src
../bin/echoargc
  argv[0]="../bin/echoargc"
cd ~/junk
~/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
~whitis/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
alias echoit=~/bin/echoargc
echoit
  argv[0]="/home/whitis/bin/echoargc"
echoarg=~/bin/echoargc
$echoarg
  argv[0]="/home/whitis/bin/echoargc"
ln -s ~/bin/echoargc junk1
./junk1
  argv[0]="./junk1"
ln -s /home/whitis/bin/echoargc junk2
./junk2
  argv[0]="./junk2"
ln -s junk1 junk3
./junk3
  argv[0]="./junk3"


gnome-desktop-item-edit --create-new ~/Desktop
# interactive, create desktop link, then click on it
  argv[0]="/home/whitis/bin/echoargc"
# interactive, right click on gnome application menu, pick edit menus
# add menu item for echoargc, then run it from gnome menu
 argv[0]="/home/whitis/bin/echoargc"

 cat ./testargcscript 2>&1 | sed -e 's/^/    /g'
#!/bin/bash
# echoargc is in ~/bin/echoargc
# bin is in path
shopt -s expand_aliases
set -v
cat ~/src/echoargc.c
tcc -o ~/bin/echoargc ~/src/echoargc.c
cd ~
/home/whitis/bin/echoargc
echoargc
bin/echoargc
bin//echoargc
bin/./echoargc
src/../bin/echoargc
cd ~/bin
*echo*
e?hoargc
./echoargc
cd ~/src
../bin/echoargc
cd ~/junk
~/bin/echoargc
~whitis/bin/echoargc
alias echoit=~/bin/echoargc
echoit
echoarg=~/bin/echoargc
$echoarg
ln -s ~/bin/echoargc junk1
./junk1
ln -s /home/whitis/bin/echoargc junk2
./junk2
ln -s junk1 junk3
./junk3

These examples illustrate that the techniques described in this post should work in a wide range of circumstances and why some of the steps are necessary.

EDIT: Now, the program that prints argv[0] has been updated to actually find itself.

// Copyright 2015 by Mark Whitis.  License=MIT style
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <assert.h>
#include <string.h>
#include <errno.h>

// "look deep into yourself, Clarice"  -- Hanibal Lector
char findyourself_save_pwd[PATH_MAX];
char findyourself_save_argv0[PATH_MAX];
char findyourself_save_path[PATH_MAX];
char findyourself_path_separator='/';
char findyourself_path_separator_as_string[2]="/";
char findyourself_path_list_separator[8]=":";  // could be ":; "
char findyourself_debug=0;

int findyourself_initialized=0;

void findyourself_init(char *argv0)
{

  getcwd(findyourself_save_pwd, sizeof(findyourself_save_pwd));

  strncpy(findyourself_save_argv0, argv0, sizeof(findyourself_save_argv0));
  findyourself_save_argv0[sizeof(findyourself_save_argv0)-1]=0;

  strncpy(findyourself_save_path, getenv("PATH"), sizeof(findyourself_save_path));
  findyourself_save_path[sizeof(findyourself_save_path)-1]=0;
  findyourself_initialized=1;
}


int find_yourself(char *result, size_t size_of_result)
{
  char newpath[PATH_MAX+256];
  char newpath2[PATH_MAX+256];

  assert(findyourself_initialized);
  result[0]=0;

  if(findyourself_save_argv0[0]==findyourself_path_separator) {
    if(findyourself_debug) printf("  absolute path\n");
     realpath(findyourself_save_argv0, newpath);
     if(findyourself_debug) printf("  newpath=\"%s\"\n", newpath);
     if(!access(newpath, F_OK)) {
        strncpy(result, newpath, size_of_result);
        result[size_of_result-1]=0;
        return(0);
     } else {
    perror("access failed 1");
      }
  } else if( strchr(findyourself_save_argv0, findyourself_path_separator )) {
    if(findyourself_debug) printf("  relative path to pwd\n");
    strncpy(newpath2, findyourself_save_pwd, sizeof(newpath2));
    newpath2[sizeof(newpath2)-1]=0;
    strncat(newpath2, findyourself_path_separator_as_string, sizeof(newpath2));
    newpath2[sizeof(newpath2)-1]=0;
    strncat(newpath2, findyourself_save_argv0, sizeof(newpath2));
    newpath2[sizeof(newpath2)-1]=0;
    realpath(newpath2, newpath);
    if(findyourself_debug) printf("  newpath=\"%s\"\n", newpath);
    if(!access(newpath, F_OK)) {
        strncpy(result, newpath, size_of_result);
        result[size_of_result-1]=0;
        return(0);
     } else {
    perror("access failed 2");
      }
  } else {
    if(findyourself_debug) printf("  searching $PATH\n");
    char *saveptr;
    char *pathitem;
    for(pathitem=strtok_r(findyourself_save_path, findyourself_path_list_separator,  &saveptr); pathitem; pathitem=strtok_r(NULL, findyourself_path_list_separator, &saveptr) ) {
       if(findyourself_debug>=2) printf("pathitem=\"%s\"\n", pathitem);
       strncpy(newpath2, pathitem, sizeof(newpath2));
       newpath2[sizeof(newpath2)-1]=0;
       strncat(newpath2, findyourself_path_separator_as_string, sizeof(newpath2));
       newpath2[sizeof(newpath2)-1]=0;
       strncat(newpath2, findyourself_save_argv0, sizeof(newpath2));
       newpath2[sizeof(newpath2)-1]=0;
       realpath(newpath2, newpath);
       if(findyourself_debug) printf("  newpath=\"%s\"\n", newpath);
      if(!access(newpath, F_OK)) {
          strncpy(result, newpath, size_of_result);
          result[size_of_result-1]=0;
          return(0);
      }
    } // end for
    perror("access failed 3");

  } // end else
  // if we get here, we have tried all three methods on argv[0] and still haven't succeeded.   Include fallback methods here.
  return(1);
}

main(int argc, char **argv)
{
  findyourself_init(argv[0]);

  char newpath[PATH_MAX];
  printf("  argv[0]=\"%s\"\n", argv[0]);
  realpath(argv[0], newpath);
  if(strcmp(argv[0],newpath)) { printf("  realpath=\"%s\"\n", newpath); }
  find_yourself(newpath, sizeof(newpath));
  if(1 || strcmp(argv[0],newpath)) { printf("  findyourself=\"%s\"\n", newpath); }
  sleep(1);  /* in case run from desktop */
}

And here is the output which demonstrates that in every one of the previous tests it actually did find itself.

tcc -o ~/bin/echoargc ~/src/echoargc.c
cd ~
/home/whitis/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
echoargc
  argv[0]="echoargc"
  realpath="/home/whitis/echoargc"
  findyourself="/home/whitis/bin/echoargc"
bin/echoargc
  argv[0]="bin/echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
bin//echoargc
  argv[0]="bin//echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
bin/./echoargc
  argv[0]="bin/./echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
src/../bin/echoargc
  argv[0]="src/../bin/echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
cd ~/bin
*echo*
  argv[0]="echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
e?hoargc
  argv[0]="echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
./echoargc
  argv[0]="./echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
cd ~/src
../bin/echoargc
  argv[0]="../bin/echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
cd ~/junk
~/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
~whitis/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
alias echoit=~/bin/echoargc
echoit
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
echoarg=~/bin/echoargc
$echoarg
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
rm junk1 junk2 junk3
ln -s ~/bin/echoargc junk1
./junk1
  argv[0]="./junk1"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
ln -s /home/whitis/bin/echoargc junk2
./junk2
  argv[0]="./junk2"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
ln -s junk1 junk3
./junk3
  argv[0]="./junk3"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"

The two GUI launches described above also correctly find the program.

There is one potential pitfall. The access() function drops permissions if the program is setuid before testing. If there is a situation where the program can be found as an elevated user but not as a regular user, then there might be a situation where these tests would fail, although it is unlikely the program could actually be executed under those circumstances. One could use euidaccess() instead. It is possible, however, that it might find an inaccessable program earlier on path than the actual user could.

🌐
GitMemory
gitmemory.com › issue › chromelyapps › Chromely › 146 › 575142802
How to run demo app on linux
2019-12-26 16:35:48.645 [INFO] Running Linux chromium 77.0.3865.120 On CefGlue child process launch arguments: /proc/self/exe --type=gpu-process --field-trial-handle=3668954390965190632,6758368075914346220,131072 --disable-features=OutOfBlinkCors --no-sandbox --locales-dir-path=/home/cat/RiderProjects/aaa/bin/Debug/netcoreapp3.1/linux-x64/locales --log-file=logs\chromely.cef_20191226.log --log-severity=info --resources-dir-path=/home/cat/RiderProjects/aaa/bin/Debug/netcoreapp3.1/linux-x64/ --lang=en-US --default-encoding=utf-8 --allow-file-access-from-files --allow-universal-access-from-files
🌐
CEF Forum
magpcss.org › ceforum › viewtopic.php
CEF Forum • Using the CEF C API without CefClient
December 24, 2012 - Invoked with...["/proc/self/exe", "--type=gpu-process", "--channel=17415.0.213110064", "--no-sandbox", "--lang=en-US", "--locales-dir-path=/home/avishek/Code/chromium-tar/home/src_tarball/tarball/chromium/src/cef/binary_distrib/cef_binary_3.1339.959_linux/Debug/locales", "--log-file=./chro...
Top answer
1 of 3
9

Instead of using readlink to discover the path to your own executable, you can directly call open on /proc/self/exe. Since the kernel already has an open fd to processes that are currently executing, this will give you an fd regardless of whether the path has been replaced with a new executable or not.

Next, you can use fexecve instead of execv which accepts an fd parameter instead of a filename parameter for the executable.

int fd = open("/proc/self/exe", O_RDONLY);
fexecve(fd, argv, envp);

Above code omits error handling for brevity.

2 of 3
3

One solution is at executable startup (e.g. near the beginning of main()) to read the value of the link /proc/self/exe once and store it statically for future use:

  static string savedBinary;
  static bool initialized = false;

  // To deal with issue of long running executable having its binary replaced
  // with a newer one on disk, we compute the resolved binary once at startup.
  if (!initialized) {
    const size_t bufSize = PATH_MAX + 1;
    char dirNameBuffer[bufSize];
    // Read the symbolic link '/proc/self/exe'.
    const char *linkName = "/proc/self/exe";
    const int ret = int(readlink(linkName, dirNameBuffer, bufSize - 1));

    savedBinary = dirNameBuffer;

    // On at least Linux, if the executable is replaced, readlink() of
    // "/proc/self/exe" gives "/usr/local/bin/flume (deleted)".
    // Therefore, we just compute the binary location statically once at
    // startup, before it can possibly be replaced, but we leave this code
    // here as an extra precaution.
    const string deleted(" (deleted)");
    const size_t deletedSize = deleted.size();
    const size_t pathSize = savedBinary.size();

    if (pathSize > deletedSize) {
      const size_t matchPos = pathSize - deletedSize;

      if (0 == savedBinary.compare(matchPos, deletedSize, deleted)) {
        // Deleted original binary, Issue warning, throw an exception, or exit.
        // Or cludge the original path with: savedBinary.erase(matchPos);
      }
    }
    initialized = true;
  }

  // Use savedBinary value.

In this way, it is very unlikely that the original executable would be replaced within the microseconds of main() caching the path to its binary. Thus, a long running application (e.g. hours or days) could get replaced on disk, but per the original question, it could fork() and execv() to the updated binary that perhaps has a bug fix. This has the added benefit of working across platforms, and thus the differing Macintosh code to read the binary path could be likewise protected from binary replacement after startup.

WARNING editors note: readlink does not null terminate the string, so the above program may or may not work accidentally if the buffer was not filled with zeros before calling readlink

🌐
GitHub
github.com › sindresorhus › pageres › issues › 377
Zombie process killing memory and cpu · Issue #377 · sindresorhus/pageres
October 5, 2019 - Sl 03:24 0:07 /app/node_modules/capture-website/node_modules/puppeteer/.local-chromium/linux-686378/chrome-linux/chrome --type=gpu-process --field-trial-handle=16199624769558228363,5769851294306486545,131072 --enable-features=NetworkService,NetworkServiceInProcess --disable-features=BlinkG...
Author   sindresorhus
🌐
The Linux Kernel
docs.kernel.org › filesystems › proc.html
The /proc Filesystem — The Linux Kernel documentation
The directory /proc contains (among other things) one subdirectory for each process running on the system, which is named after the process ID (PID). The link ‘self’ points to the process reading the file system.