Android异常su进程处理:窃取父进程文件描述符方案咨询
解决Android中su进程stdin/stdout异常的方案
问题根源分析
你遇到的核心问题是:su进程作为中间层接管了终端的stdin/stdout,且未正确处理进程组与终端控制关系。当按下ctrl + c发送SIGINT信号时,信号会被传递给终端的前台进程组——而su及其所有子进程都属于该组,因此会被全部终止。绕开su的输入中转,让新shell直接绑定终端,是完全可行的解决思路。
你的方案可行性验证
你的思路方向完全正确:通过fork独立的shell进程,让它直接关联终端设备、脱离su的进程组控制,这样信号只会传递给新shell,不会影响其他进程。下面我把具体实现细节拆解清楚:
C程序实现步骤详解
1. 定位父su进程ID
如果你的C程序在su启动的环境中运行,直接调用getppid()就能拿到su的PID。若需要更严谨验证,可读取/proc/<pid>/comm文件确认进程名称为"su":
#include <unistd.h> #include <stdio.h> #include <string.h> pid_t get_su_parent_pid() { pid_t ppid = getppid(); char comm_path[64]; snprintf(comm_path, sizeof(comm_path), "/proc/%d/comm", ppid); FILE* fp = fopen(comm_path, "r"); if (!fp) return -1; char comm[32]; fgets(comm, sizeof(comm), fp); fclose(fp); // 去掉换行符 comm[strcspn(comm, "\n")] = '\0'; if (strcmp(comm, "su") == 0) { return ppid; } return -1; }
2. 获取终端设备文件描述符
用ttyname()获取当前终端路径(比如/dev/pts/1),再打开它获得可读写的文件描述符:
#include <fcntl.h> int get_tty_fd() { const char* tty_path = ttyname(STDIN_FILENO); if (!tty_path) { perror("Failed to get terminal path"); return -1; } int fd = open(tty_path, O_RDWR); if (fd == -1) { perror("Failed to open terminal"); } return fd; }
3. Fork并启动独立的Shell进程
这一步的核心是让新Shell成为终端的前台进程组,脱离su的进程组控制。代码如下:
#include <sys/types.h> #include <sys/wait.h> #include <signal.h> int main() { // 获取su父进程ID(可选,如需后续处理su进程) pid_t su_pid = get_su_parent_pid(); if (su_pid == -1) { fprintf(stderr, "Could not find parent su process\n"); // 即使找不到,仍可尝试启动新shell } // 获取终端文件描述符 int tty_fd = get_tty_fd(); if (tty_fd == -1) { return 1; } // Fork新进程 pid_t child_pid = fork(); if (child_pid == 0) { // 子进程:创建新进程组并成为前台进程组 setpgid(0, 0); // 将自身设为新进程组组长 if (tcsetpgrp(tty_fd, getpid()) == -1) { perror("Failed to set terminal foreground process group"); _exit(1); } // 重定向标准输入输出到终端 dup2(tty_fd, STDIN_FILENO); dup2(tty_fd, STDOUT_FILENO); dup2(tty_fd, STDERR_FILENO); close(tty_fd); // 替换为Shell(若设备有bash可替换为/system/bin/bash) execl("/system/bin/sh", "sh", NULL); // 若execl执行失败,退出子进程 perror("Failed to execute shell"); _exit(1); } else if (child_pid > 0) { // 父进程:可选择等待子进程结束,或直接退出让子进程独立运行 // 如需终止su进程,可在此发送信号:kill(su_pid, SIGTERM); waitpid(child_pid, NULL, 0); } else { perror("Failed to fork process"); close(tty_fd); return 1; } close(tty_fd); return 0; }
关键注意事项
- 编译适配:需用Android NDK编译程序,确保目标架构与设备匹配(比如arm64-v8a),编译命令示例:
$NDK_PATH/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang su_fix.c -o su_fix - 进程组设置:
setpgid和tcsetpgrp是核心,缺少这两步,新Shell仍会属于su的进程组,ctrl+c仍会终止所有进程。 - su进程处理:若不需要su继续运行,可在父进程中向su_pid发送
SIGTERM信号;若不主动终止,su进程会成为孤儿进程被init收养,失去终端控制权后通常会自行退出。
内容的提问来源于stack exchange,提问作者twosixonetwo
相关产品推荐
相关产品推荐

