我尝试使用Java编写的应用程序在Android上运行shell脚本。我希望能够以root身份运行Android命令,例如am
或pm
,之前需要export LD_LIBRARY_PATH=/system/lib
命令。不幸的是,export
命令不起作用。这是我写的Java代码:
private String readScript(String scriptPath) {
String line = null;
StringBuffer content = new StringBuffer();
try {
// Read the script line by line
BufferedReader reader = new BufferedReader(new FileReader(scriptPath));
while ((line = reader.readLine()) != null) {
content.append(line).append('\n');
}
content.append("exit").append('\n');
reader.close();
} catch (IOException e) {
Log.d(UpdateService.LOG_NAME, "Impossible to read script : " + e.getMessage());
}
return content.toString();
}
public StringBuffer runScript(String scriptPath) {
String line = null;
StringBuffer output = new StringBuffer();
try {
// Start the shell process
ProcessBuilder pb = new ProcessBuilder("su");
pb.redirectErrorStream(true);
pb.directory(new File(context.getApplicationInfo().dataDir));
pb.environment().put("LD_LIBRARY_PATH", "/system/lib");
Process process = pb.start();
// Execute the script
OutputStreamWriter os = new OutputStreamWriter(process.getOutputStream());
os.write(readScript(scriptPath));
os.flush();
os.close();
// Get the output of the commands
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((line = in.readLine()) != null) {
output.append(line).append('\n');
}
in.close();
} catch (Exception e) {
Log.d(UpdateService.LOG_NAME, "Error while executing " + scriptPath + " : " + e.getMessage());
}
return output;
}
我简单地称之为:
Shell = new Shell();
StringBuffer output = shell.runScript("script.sh");
我尝试运行的脚本:
export LD_LIBRARY_PATH=/system/lib
printenv
am start -W -n com.android.settings/.Settings\$InputMethodAndLanguageSettingsActivity
给我输出:
_=/system/bin/printenv
ANDROID_BOOTLOGO=1
ANDROID_PROPERTY_WORKSPACE=9,32768
LOOP_MOUNTPOINT=/mnt/obb
EXTERNAL_STORAGE=/mnt/sdcard
ANDROID_DATA=/data
RANDOM=21985
ANDROID_SOCKET_zygote=10
ASEC_MOUNTPOINT=/mnt/asec
BOOTCLASSPATH=/system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/apache-xml.jar:/system/framework/filterfw.jar
ANDROID_ROOT=/system
ANDROID_ASSETS=/system/app
PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin
Segmentation fault
正如您所看到的,使用environment()
ProcessBuilder
方法设置的环境变量似乎未设置,脚本中的export
也无法正常工作。< / p>
对我做错了什么的任何想法? 谢谢!
修改
根据建议,我还尝试了exec()
的版本:
public StringBuffer runScript(String scriptPath) {
String line = null;
StringBuffer output = new StringBuffer();
try {
// Start the shell process
Process process = Runtime.getRuntime().exec("su");
// Execute the script commands
OutputStreamWriter os = new OutputStreamWriter(process.getOutputStream());
os.write(readScript(scriptPath));
os.flush();
os.close();
// Get the output of the command
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((line = in.readLine()) != null) {
output.append(line).append('\n');
}
in.close();
process.waitFor();
} catch (Exception ex) {
Log.d(UpdateService.LOG_NAME, "Error while executing " + scriptPath + ".");
}
return output;
}
这次我没有stderr
(分段错误)得到相同的输出:
_=/system/bin/printenv
ANDROID_BOOTLOGO=1
ANDROID_PROPERTY_WORKSPACE=9,32768
LOOP_MOUNTPOINT=/mnt/obb
EXTERNAL_STORAGE=/mnt/sdcard
ANDROID_DATA=/data
RANDOM=21985
ANDROID_SOCKET_zygote=10
ASEC_MOUNTPOINT=/mnt/asec
BOOTCLASSPATH=/system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/apache-xml.jar:/system/framework/filterfw.jar
ANDROID_ROOT=/system
ANDROID_ASSETS=/system/app
PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin
因此导出LD_LIBRARY_PATH=/system/lib
仍然无效。在我看来,ProcessBuilder
是一个更好的方式来启动流程,因为它致力于它。是不是?
EDIT2
am
和pm
实际上是启动java可执行文件的包装器。我试图实现这个包装器的C版本来创建一个适用于任何环境的完整环境。这是am的内容:
# Script to start "am" on the device, which has a very rudimentary
# shell.
#
base=/system
export CLASSPATH=$base/framework/am.jar
exec app_process $base/bin com.android.commands.am.Am "$@"
这是我的C版:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(int argc, const char *argv[], const char *envp[]) {
const char **p;
const int NUM_ARG = 3;
const char *APP_PROCESS = "/system/bin/app_process";
const char *BIN = "/system/bin";
const char *AM = "com.android.commands.am.Am";
const int NUM_ENV = 11;
const char *ENV[] = {
"PATH=/usr/bin:/usr/sbin:/bin:/sbin:/system/sbin:/system/bin:/system/xbin:/system/xbin/bb:/data/local/bin",
"SHELL=/system/bin/sh",
"MKSH=/system/bin/sh",
"HOME=/data",
"HOSTNAME=android",
"USER=root",
"LOGNAME=root",
"TERM=xterm",
"LD_LIBRARY_PATH=/system/lib/",
"CLASSPATH=/system/framework/am.jar",
NULL
};
int i = 0;
char *a[argc + NUM_ARG];
a[i] = (char *)malloc(strlen(APP_PROCESS) + 1);
strcpy(a[i++], APP_PROCESS);
a[i] = (char *)malloc(strlen(BIN) + 1);
strcpy(a[i++], BIN);
a[i] = (char *)malloc(strlen(AM) + 1);
strcpy(a[i++], AM);
p = argv + 1;
for (; i < argc + NUM_ARG - 1; i++) {
a[i] = (char *)malloc(strlen(*p) + 1);
strcpy(a[i], *p++);
}
a[i] = (char *)malloc(1);
a[i] = NULL;
p = envp;
int envc = 0;
while (*p++ != NULL) envc++;
char *v[envc + NUM_ENV];
for (i = 0; i < envc; i++) {
v[i] = (char *)malloc(strlen(envp[i]) + 1);
strcpy(v[i], envp[i]);
}
p = ENV;
while (*p != NULL) {
v[i] = (char *)malloc(strlen(*p) + 1);
strcpy(v[i++], *p++);
}
v[i] = (char *)malloc(1);
v[i] = NULL;
execve(APP_PROCESS, a, v);
fprintf(stderr, "am: %s error: %s\n", APP_PROCESS, strerror(errno));
for (i = 0; i < argc + NUM_ARG; i++) {
free(a[i]);
}
for (i = 0; i < envc + NUM_ENV; i++) {
free(v[i]);
}
return 1;
}
它与ADB或SSH完美配合,但即使使用此实现,我也会得到一个&#34;分段错误&#34;执行execve()
后的ProcessBuilder
行。为什么???
EDIT3
好的,我查看了脚本init.rc的内容,其中列出了环境变量。正确设置了LD_LIBRARY_PATH变量。
on init
sysclktz 0
loglevel 7
mkdir /system
mkdir /data 0771 system system
mkdir /cache 0770 system cache
mkdir /config 0500 root root
# Setup the global environment
export PATH /sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin
export LD_LIBRARY_PATH /vendor/lib:/system/lib
export ANDROID_BOOTLOGO 1
export ANDROID_ROOT /system
export ANDROID_ASSETS /system/app
export ANDROID_DATA /data
export EXTERNAL_STORAGE /mnt/sdcard
export ASEC_MOUNTPOINT /mnt/asec
export LOOP_MOUNTPOINT /mnt/obb
export BOOTCLASSPATH /system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/apache-xml.jar:/system/framework/filterfw.jar
但是......出于某种原因,根据具体情况,它们会被更改或删除。例如,如果我通过ADB连接:
$ env
_=/system/xbin/env
ANDROID_BOOTLOGO=1
ANDROID_PROPERTY_WORKSPACE=9,32768
LOOP_MOUNTPOINT=/mnt/obb
PS1=$(precmd)$USER@$HOSTNAME:${PWD:-?} $
USER=shell
EXTERNAL_STORAGE=/mnt/sdcard
ANDROID_DATA=/data
RANDOM=22124
TERM=vt100
MKSH=/system/bin/sh
HOME=/data
LD_LIBRARY_PATH=/vendor/lib:/system/lib
ASEC_MOUNTPOINT=/mnt/asec
HOSTNAME=android
BOOTCLASSPATH=/system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/apache-xml.jar:/system/framework/filterfw.jar
ANDROID_ROOT=/system
SHELL=/system/bin/sh
ANDROID_ASSETS=/system/app
PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin
如果我通过SSH连接:
$ env
_=/system/xbin/env
ANDROID_PROPERTY_WORKSPACE=9,32768
ANDROID_BOOTLOGO=1
PS1=$(precmd)$USER@$HOSTNAME:${PWD:-?} $
USER=shell
EXTERNAL_STORAGE=/mnt/sdcard
LOGNAME=shell
ANDROID_DATA=/data
RANDOM=4546
TERM=xterm
SHELL=/system/bin/sh
MKSH=/system/bin/sh
HOME=/data/data/com.teslacoilsw.quicksshd/home
HOSTNAME=android
BOOTCLASSPATH=/system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/apache-xml.jar:/system/framework/filterfw.jar
ANDROID_ROOT=/system
ANDROID_ASSETS=/system/app
PATH=/data/data/com.teslacoilsw.quicksshd/dropbear:/usr/bin:/usr/sbin:/bin:/sbin:/system/sbin:/system/bin:/system/xbin:/system/xbin/bb:/data/local/bin
如果我通过Java运行命令:
$ env
_=/system/bin/printenv
ANDROID_BOOTLOGO=1
ANDROID_PROPERTY_WORKSPACE=9,32768
LOOP_MOUNTPOINT=/mnt/obb
EXTERNAL_STORAGE=/mnt/sdcard
ANDROID_DATA=/data
RANDOM=7424
ANDROID_SOCKET_zygote=10
ASEC_MOUNTPOINT=/mnt/asec
BOOTCLASSPATH=/system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/apache-xml.jar:/system/framework/filterfw.jar
ANDROID_ROOT=/system
ANDROID_ASSETS=/system/app
PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin
通过ADB,命令am
非常有效。通过SSH,如果我运行export LD_LIBRARY_PATH=/system/lib/
,它可以工作。在Java中它永远不会起作用:/