Skip to content

Commit 3d149d3

Browse files
TheCoryBarkerfmeum
andauthored
Add foundations for Android support (#587)
This includes the necessary changes to the native launcher, driver and runtime, but does not yet include a way to instrument the application under test - Android doesn't support Java agents. Since building the Android targets requires a local installation of the Android SDK and NDK, these targets aren't built by default and only requested in the Linux CI pipeline. Co-authored-by: Fabian Meumertzheim <fabian@meumertzhe.im>
1 parent 27ef3af commit 3d149d3

27 files changed

+501
-55
lines changed

.bazelrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ build:ci --features=layering_check
2525
build --java_language_version=8
2626
build --tool_java_language_version=8
2727

28+
# Android
29+
build:android_arm --incompatible_enable_android_toolchain_resolution
30+
build:android_arm --android_platforms=//:android_arm64
31+
build:android_arm --copt=-D_ANDROID
32+
2833
# Windows
2934
# Only compiles with clang on Windows.
3035
build:windows --extra_toolchains=@local_config_cc//:cc-toolchain-x64_windows-clang-cl

.github/workflows/run-all-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ jobs:
3838
- os: ubuntu-20.04
3939
arch: "linux"
4040
cache: "/home/runner/.cache/bazel-disk"
41+
bazel_args: "//launcher/android:jazzer_android"
4142
- os: ubuntu-20.04
4243
jdk: 19
4344
# Workaround for https://github.com/bazelbuild/bazel/issues/14502

BUILD.bazel

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,12 @@ alias(
2424
name = "jazzer",
2525
actual = "//launcher:jazzer",
2626
)
27+
28+
platform(
29+
name = "android_arm64",
30+
constraint_values = [
31+
"@platforms//cpu:arm64",
32+
"@platforms//os:android",
33+
],
34+
visibility = ["//:__subpackages__"],
35+
)

WORKSPACE.bazel

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,20 @@ http_archive(
8686
url = "https://github.com/CodeIntelligenceTesting/rules_jvm/archive/refs/tags/java-8.tar.gz",
8787
)
8888

89+
http_archive(
90+
name = "build_bazel_rules_android",
91+
sha256 = "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806",
92+
strip_prefix = "rules_android-0.1.1",
93+
urls = ["https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip"],
94+
)
95+
96+
http_archive(
97+
name = "rules_android_ndk",
98+
sha256 = "0fcaeed391bfc0ee784ab0365312e6c59fe75db9df3a67e8708c606e2a9cfd90",
99+
strip_prefix = "rules_android_ndk-79923720aef601fad89c94e8802f5d77c1b73c5d",
100+
url = "https://github.com/bazelbuild/rules_android_ndk/archive/79923720aef601fad89c94e8802f5d77c1b73c5d.zip",
101+
)
102+
89103
http_file(
90104
name = "genhtml",
91105
downloaded_file_path = "genhtml",
@@ -171,3 +185,25 @@ rules_pkg_dependencies()
171185
load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
172186

173187
protobuf_deps()
188+
189+
http_file(
190+
name = "jacocoagent",
191+
downloaded_file_path = "jacocoagent.jar",
192+
sha256 = "67de51e9ca1db044f3a3d10613518befb02e8eee1015f2ff6d56cfb9d4506546",
193+
urls = ["https://repo1.maven.org/maven2/org/jacoco/org.jacoco.agent/0.8.8/org.jacoco.agent-0.8.8-runtime.jar"],
194+
)
195+
196+
http_file(
197+
name = "jacococli",
198+
downloaded_file_path = "jacococli.jar",
199+
sha256 = "c449591174982bbc003d1290003fcbc7b939215436922d2f0f25239d110d531a",
200+
urls = ["https://repo1.maven.org/maven2/org/jacoco/org.jacoco.cli/0.8.8/org.jacoco.cli-0.8.8-nodeps.jar"],
201+
)
202+
203+
load("//third_party/android:android_configure.bzl", "android_configure")
204+
205+
android_configure(name = "configure_android_rules")
206+
207+
load("@configure_android_rules//:android_configure.bzl", "android_workspace")
208+
209+
android_workspace()

launcher/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ cc_library(
1717
data = [
1818
"//src/main/java/com/code_intelligence/jazzer:jazzer_standalone_deploy.jar",
1919
],
20+
linkopts = select({
21+
"@platforms//os:android": ["-ldl"],
22+
"//conditions:default": [],
23+
}),
2024
deps = [
2125
"@bazel_tools//tools/cpp/runfiles",
2226
"@com_google_absl//absl/strings",
@@ -29,6 +33,7 @@ cc_binary(
2933
name = "jazzer_single_arch",
3034
linkstatic = True,
3135
tags = ["manual"],
36+
visibility = ["//launcher/android:__pkg__"],
3237
deps = [":jazzer_main"],
3338
)
3439

launcher/android/AndroidManifest.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2+
package="com.code_intelligence.jazzer"
3+
android:versionCode="1"
4+
android:versionName="1.0" >
5+
</manifest>

launcher/android/BUILD.bazel

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
load("//bazel:compat.bzl", "SKIP_ON_WINDOWS")
2+
3+
android_library(
4+
name = "jazzer_android_lib",
5+
data = [
6+
"//launcher:jazzer_single_arch",
7+
"//src/main/java/com/code_intelligence/jazzer/android:jazzer_standalone_android.apk",
8+
],
9+
tags = ["manual"],
10+
target_compatible_with = SKIP_ON_WINDOWS,
11+
)
12+
13+
android_binary(
14+
name = "jazzer_android",
15+
manifest = ":android_manifest",
16+
min_sdk_version = 26,
17+
tags = ["manual"],
18+
target_compatible_with = SKIP_ON_WINDOWS,
19+
visibility = ["//visibility:public"],
20+
deps = [
21+
":jazzer_android_lib",
22+
],
23+
)
24+
25+
filegroup(
26+
name = "android_manifest",
27+
srcs = ["AndroidManifest.xml"],
28+
tags = ["manual"],
29+
visibility = [
30+
"//visibility:public",
31+
],
32+
)

launcher/jvm_tooling.cpp

Lines changed: 93 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515
#include "jvm_tooling.h"
1616

17+
#if defined(_ANDROID)
18+
#include <dlfcn.h>
19+
#endif
20+
1721
#include <cstdlib>
1822
#include <fstream>
1923
#include <iostream>
@@ -120,20 +124,74 @@ std::vector<std::string> splitEscaped(const std::string &str) {
120124

121125
namespace jazzer {
122126

123-
JVM::JVM(const std::string &executable_path) {
127+
#if defined(_ANDROID)
128+
typedef jint (*JNI_CreateJavaVM_t)(JavaVM **, JNIEnv **, void *);
129+
JNI_CreateJavaVM_t LoadAndroidVMLibs() {
130+
std::cout << "Loading Android libraries" << std::endl;
131+
132+
void *art_so = nullptr;
133+
art_so = dlopen("libnativehelper.so", RTLD_NOW);
134+
135+
if (art_so == nullptr) {
136+
std::cerr << "Could not find ART library" << std::endl;
137+
exit(1);
138+
}
139+
140+
typedef void *(*JniInvocationCreate_t)();
141+
JniInvocationCreate_t JniInvocationCreate =
142+
reinterpret_cast<JniInvocationCreate_t>(
143+
dlsym(art_so, "JniInvocationCreate"));
144+
if (JniInvocationCreate == nullptr) {
145+
std::cout << "JniInvocationCreate is null" << std::endl;
146+
exit(1);
147+
}
148+
149+
void *impl = JniInvocationCreate();
150+
typedef bool (*JniInvocationInit_t)(void *, const char *);
151+
JniInvocationInit_t JniInvocationInit =
152+
reinterpret_cast<JniInvocationInit_t>(dlsym(art_so, "JniInvocationInit"));
153+
if (JniInvocationInit == nullptr) {
154+
std::cout << "JniInvocationInit is null" << std::endl;
155+
exit(1);
156+
}
157+
158+
JniInvocationInit(impl, nullptr);
159+
160+
constexpr char create_jvm_symbol[] = "JNI_CreateJavaVM";
161+
typedef jint (*JNI_CreateJavaVM_t)(JavaVM **, JNIEnv **, void *);
162+
JNI_CreateJavaVM_t JNI_CreateArtVM =
163+
reinterpret_cast<JNI_CreateJavaVM_t>(dlsym(art_so, create_jvm_symbol));
164+
if (JNI_CreateArtVM == nullptr) {
165+
std::cout << "JNI_CreateJavaVM is null" << std::endl;
166+
exit(1);
167+
}
168+
169+
return JNI_CreateArtVM;
170+
}
171+
#endif
172+
173+
std::string GetClassPath(const std::string &executable_path) {
124174
// combine class path from command line flags and JAVA_FUZZER_CLASSPATH env
125175
// variable
126176
std::string class_path = absl::StrFormat("-Djava.class.path=%s", FLAGS_cp);
127177
const auto class_path_from_env = std::getenv("JAVA_FUZZER_CLASSPATH");
128178
if (class_path_from_env) {
129179
class_path += absl::StrCat(ARG_SEPARATOR, class_path_from_env);
130180
}
181+
131182
class_path +=
132183
absl::StrCat(ARG_SEPARATOR, getInstrumentorAgentPath(executable_path));
184+
return class_path;
185+
}
186+
187+
JVM::JVM(const std::string &executable_path) {
188+
std::string class_path = GetClassPath(executable_path);
133189

134190
std::vector<JavaVMOption> options;
135191
options.push_back(
136192
JavaVMOption{.optionString = const_cast<char *>(class_path.c_str())});
193+
194+
#if !defined(_ANDROID)
137195
// Set the maximum heap size to a value that is slightly smaller than
138196
// libFuzzer's default rss_limit_mb. This prevents erroneous oom reports.
139197
options.push_back(JavaVMOption{.optionString = (char *)"-Xmx1800m"});
@@ -148,44 +206,66 @@ JVM::JVM(const std::string &executable_path) {
148206
JavaVMOption{.optionString = (char *)"-XX:+IgnoreUnrecognizedVMOptions"});
149207
options.push_back(
150208
JavaVMOption{.optionString = (char *)"-XX:+CriticalJNINatives"});
209+
#endif
151210

152-
// Add additional JVM options set through JAVA_OPTS.
153211
std::vector<std::string> java_opts_args;
154212
const char *java_opts = std::getenv("JAVA_OPTS");
155213
if (java_opts != nullptr) {
156214
// Mimic the behavior of the JVM when it sees JAVA_TOOL_OPTIONS.
157215
std::cerr << "Picked up JAVA_OPTS: " << java_opts << std::endl;
216+
158217
java_opts_args = absl::StrSplit(java_opts, ' ');
159218
for (const std::string &java_opt : java_opts_args) {
160219
options.push_back(
161220
JavaVMOption{.optionString = const_cast<char *>(java_opt.c_str())});
162221
}
163222
}
164223

165-
// add additional jvm options set through command line flags
224+
// Add additional jvm options set through command line flags.
225+
// Keep the vectors in scope as they contain the strings backing the C strings
226+
// added to options.
166227
std::vector<std::string> jvm_args;
167228
if (!FLAGS_jvm_args.empty()) {
168229
jvm_args = splitEscaped(FLAGS_jvm_args);
230+
for (const auto &arg : jvm_args) {
231+
options.push_back(
232+
JavaVMOption{.optionString = const_cast<char *>(arg.c_str())});
233+
}
169234
}
170-
for (const auto &arg : jvm_args) {
171-
options.push_back(
172-
JavaVMOption{.optionString = const_cast<char *>(arg.c_str())});
173-
}
235+
174236
std::vector<std::string> additional_jvm_args;
175237
if (!FLAGS_additional_jvm_args.empty()) {
176238
additional_jvm_args = splitEscaped(FLAGS_additional_jvm_args);
239+
for (const auto &arg : additional_jvm_args) {
240+
options.push_back(
241+
JavaVMOption{.optionString = const_cast<char *>(arg.c_str())});
242+
}
177243
}
178-
for (const auto &arg : additional_jvm_args) {
179-
options.push_back(
180-
JavaVMOption{.optionString = const_cast<char *>(arg.c_str())});
181-
}
182244

183-
JavaVMInitArgs jvm_init_args = {.version = JNI_VERSION_1_8,
245+
#if !defined(_ANDROID)
246+
jint jni_version = JNI_VERSION_1_8;
247+
#else
248+
jint jni_version = JNI_VERSION_1_6;
249+
#endif
250+
251+
JavaVMInitArgs jvm_init_args = {.version = jni_version,
184252
.nOptions = (int)options.size(),
185253
.options = options.data(),
186254
.ignoreUnrecognized = JNI_FALSE};
187255

188-
auto ret = JNI_CreateJavaVM(&jvm_, (void **)&env_, &jvm_init_args);
256+
#if !defined(_ANDROID)
257+
int ret = JNI_CreateJavaVM(&jvm_, (void **)&env_, &jvm_init_args);
258+
#else
259+
JNI_CreateJavaVM_t CreateArtVM = LoadAndroidVMLibs();
260+
if (CreateArtVM == nullptr) {
261+
std::cerr << "JNI_CreateJavaVM for Android not found" << std::endl;
262+
exit(1);
263+
}
264+
265+
std::cout << "Starting Art VM" << std::endl;
266+
int ret = CreateArtVM(&jvm_, (JNIEnv_ **)&env_, &jvm_init_args);
267+
#endif
268+
189269
if (ret != JNI_OK) {
190270
throw std::runtime_error(
191271
absl::StrFormat("JNI_CreateJavaVM returned code %d", ret));

repositories.bzl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,9 @@ def jazzer_dependencies():
9898
maybe(
9999
http_archive,
100100
name = "fmeum_rules_jni",
101-
sha256 = "9bd07dade81131aa7e1ac36830cef5d2a6a076406bec53dff51ea59e0efc77a9",
102-
strip_prefix = "rules_jni-0.6.1",
103-
url = "https://github.com/fmeum/rules_jni/archive/refs/tags/v0.6.1.tar.gz",
101+
sha256 = "530a02c4d86f7bcfabd61e7de830f8c78fcb2ea70943eab8f2bfdad96620f1f5",
102+
strip_prefix = "rules_jni-0.7.0",
103+
url = "https://github.com/fmeum/rules_jni/archive/refs/tags/v0.7.0.tar.gz",
104104
)
105105

106106
maybe(

sanitizers/BUILD.bazel

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,11 @@ java_library(
55
"//sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers",
66
],
77
)
8+
9+
java_library(
10+
name = "offline_only_sanitizers",
11+
visibility = ["//visibility:public"],
12+
runtime_deps = [
13+
":sanitizers",
14+
],
15+
)

0 commit comments

Comments
 (0)