|
14 | 14 | from .pstats_collector import PstatsCollector
|
15 | 15 | from .stack_collector import CollapsedStackCollector
|
16 | 16 |
|
| 17 | + |
| 18 | +MACOS_PERMISSION_ERROR = """\ |
| 19 | +🔒 Tachyon was unable to access process memory due to insufficient permissions on Mac OS. |
| 20 | +To profile processes, you may need to run Tachyon with sudo. Note that System Integrity Protection (SIP) can block access to Python binaries in protected locations. |
| 21 | +Using a virtual environment or installing Python outside protected directories may help. |
| 22 | +""" |
| 23 | + |
| 24 | +LINUX_PERMISSION_ERROR = """ |
| 25 | +🔒 Tachyon was unable to acess process memory. This could be because tachyon |
| 26 | +has insufficient privileges (the required capability is CAP_SYS_PTRACE). |
| 27 | +Unprivileged processes cannot trace processes that they cannot send signals |
| 28 | +to or those running set-user-ID/set-group-ID programs, for security reasons. |
| 29 | +
|
| 30 | +If your uid matches the uid of the target process you want to analyze, you |
| 31 | +can do one of the following to get 'ptrace' scope permissions: |
| 32 | +
|
| 33 | +* If you are running inside a Docker container, you need to make sure you |
| 34 | + start the container using the '--cap-add=SYS_PTRACE' or '--privileged' |
| 35 | + command line arguments. Notice that this may not be enough if you are not |
| 36 | + running as 'root' inside the Docker container as you may need to disable |
| 37 | + hardening (see next points). |
| 38 | +
|
| 39 | +* Try running again with elevated permissions by running 'sudo -E !!'. |
| 40 | +
|
| 41 | +* You can disable kernel hardening for the current session temporarily (until |
| 42 | + a reboot happens) by running 'echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope'. |
| 43 | +""" |
| 44 | + |
| 45 | +WINDOWS_PERMISSION_ERROR = """ |
| 46 | +🔒 Tachyon requires administrator rights to access process memory on Windows. |
| 47 | +Please run your command prompt as Administrator and try again. |
| 48 | +""" |
| 49 | + |
| 50 | +GENERIC_PERMISSION_ERROR = """ |
| 51 | +🔒 Tachyon was unable to access the target process due to operating system restrictions or missing privileges. |
| 52 | +""" |
| 53 | + |
17 | 54 | _FREE_THREADED_BUILD = sysconfig.get_config_var("Py_GIL_DISABLED") is not None
|
18 |
| -_MAX_STARTUP_ATTEMPTS = 5 |
19 |
| -_STARTUP_RETRY_DELAY_SECONDS = 0.1 |
20 | 55 | _HELP_DESCRIPTION = """Sample a process's stack frames and generate profiling data.
|
21 | 56 | Supports the following target modes:
|
22 | 57 | - -p PID: Profile an existing process by PID
|
@@ -826,39 +861,47 @@ def main():
|
826 | 861 | elif target_count > 1:
|
827 | 862 | parser.error("only one target type can be specified: -p/--pid, -m/--module, or script")
|
828 | 863 |
|
829 |
| - if args.pid: |
830 |
| - sample( |
831 |
| - args.pid, |
832 |
| - sample_interval_usec=args.interval, |
833 |
| - duration_sec=args.duration, |
834 |
| - filename=args.outfile, |
835 |
| - all_threads=args.all_threads, |
836 |
| - limit=args.limit, |
837 |
| - sort=sort_value, |
838 |
| - show_summary=not args.no_summary, |
839 |
| - output_format=args.format, |
840 |
| - realtime_stats=args.realtime_stats, |
841 |
| - ) |
842 |
| - elif args.module or args.args: |
843 |
| - if args.module: |
844 |
| - cmd = (sys.executable, "-m", args.module, *args.args) |
845 |
| - else: |
846 |
| - cmd = (sys.executable, *args.args) |
847 |
| - |
848 |
| - # Use synchronized process startup |
849 |
| - process = _run_with_sync(cmd) |
| 864 | + try: |
| 865 | + if args.pid: |
| 866 | + sample( |
| 867 | + args.pid, |
| 868 | + sample_interval_usec=args.interval, |
| 869 | + duration_sec=args.duration, |
| 870 | + filename=args.outfile, |
| 871 | + all_threads=args.all_threads, |
| 872 | + limit=args.limit, |
| 873 | + sort=sort_value, |
| 874 | + show_summary=not args.no_summary, |
| 875 | + output_format=args.format, |
| 876 | + realtime_stats=args.realtime_stats, |
| 877 | + ) |
| 878 | + elif args.module or args.args: |
| 879 | + if args.module: |
| 880 | + cmd = (sys.executable, "-m", args.module, *args.args) |
| 881 | + else: |
| 882 | + cmd = (sys.executable, *args.args) |
850 | 883 |
|
851 |
| - # Process has already signaled readiness, start sampling immediately |
852 |
| - try: |
853 |
| - wait_for_process_and_sample(process.pid, sort_value, args) |
854 |
| - finally: |
855 |
| - if process.poll() is None: |
856 |
| - process.terminate() |
857 |
| - try: |
858 |
| - process.wait(timeout=2) |
859 |
| - except subprocess.TimeoutExpired: |
860 |
| - process.kill() |
861 |
| - process.wait() |
| 884 | + # Use synchronized process startup |
| 885 | + process = _run_with_sync(cmd) |
862 | 886 |
|
863 |
| -if __name__ == "__main__": |
864 |
| - main() |
| 887 | + # Process has already signaled readiness, start sampling immediately |
| 888 | + try: |
| 889 | + wait_for_process_and_sample(process.pid, sort_value, args) |
| 890 | + finally: |
| 891 | + if process.poll() is None: |
| 892 | + process.terminate() |
| 893 | + try: |
| 894 | + process.wait(timeout=2) |
| 895 | + except subprocess.TimeoutExpired: |
| 896 | + process.kill() |
| 897 | + process.wait() |
| 898 | + except PermissionError: |
| 899 | + if sys.platform == "darwin": |
| 900 | + print(MACOS_PERMISSION_ERROR, file=sys.stderr) |
| 901 | + elif sys.platform.startswith("linux"): |
| 902 | + print(LINUX_PERMISSION_ERROR, file=sys.stderr) |
| 903 | + elif sys.platform.startswith("win"): |
| 904 | + print(WINDOWS_PERMISSION_ERROR, file=sys.stderr) |
| 905 | + else: |
| 906 | + print(GENERIC_PERMISSION_ERROR, file=sys.stderr) |
| 907 | + sys.exit(1) |
0 commit comments