Skip to content
This repository was archived by the owner on Feb 29, 2024. It is now read-only.

Commit 6c5d51a

Browse files
authored
feat: Support App Engine Standard Java8 (#70)
* Statically link the agent .so file with libstdc++ so the agent is able to load in the Java 8 App Engine Standard environment. * Handle split jars since the GAE Java8 environment has a 32M file size limit * Support loading cdbg java agent internals jar file when it's split into multiple jars * Add the jar splitter utility and build a custom java 8 package with the internals jar file split * Add the Java 8 WEB-INF/lib and WEB-INF/classes to be auto indexed (setting extra_classpath is not an option as prior to deployment users cannot know the directory path their application will run from.
1 parent e62594e commit 6c5d51a

22 files changed

Lines changed: 956 additions & 50 deletions

File tree

.gitignore

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@
1212
# itself. If this gets fixed at some point, these rules can be removed.
1313
dist/
1414
src/agent/antlrgen
15-
src/agent/internals/target
16-
src/agent/internals-class-loader/target/
1715
src/agent/internals_class_loader_static_defs.inl
18-
src/agent/service-account-auth/target
1916
src/agent/version.txt
2017
src/codegen/target
2118
src/codegen/*.cc
@@ -24,3 +21,6 @@ third_party/gflags*
2421
third_party/glog*
2522
third_party/install
2623
third_party/jsoncpp*
24+
25+
# Maven build output
26+
target/

WORKSPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ maven_install(
3737
"org.ow2.asm:asm-commons:9.1",
3838
"org.ow2.asm:asm-util:9.1",
3939
"org.yaml:snakeyaml:1.32",
40+
"pl.pragmatists:JUnitParams:1.1.1",
4041
],
4142
repositories = [
4243
"https://maven.google.com",

src/agent/Makefile

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ JAVAC=javac -source 1.7 -target 1.7
4848
BUILD_TARGET_PATH ?= .
4949

5050
OPT_FLAGS = -O3 -D NDEBUG
51-
LDFLAGS += -shared
51+
52+
# Statically linking libstd++ helps ensure cdbg_java_agent.so will load in a
53+
# wider set of environments
54+
LDFLAGS += -shared -static-libstdc++
5255

5356
ifneq ($(OS),Darwin)
5457
# Pass these linker flags only when building with gcc (i.e., not with clang).
@@ -61,6 +64,7 @@ ANTLR_TOOL_PATH = $(THIRD_PARTY_PATH)/antlr
6164
ANTLR_CPP_LIB = $(THIRD_PARTY_PATH)/antlr/lib/cpp
6265
ANTLR_CPP_LIB_SRC = $(ANTLR_CPP_LIB)/src
6366
ANTLR_CPP_LIB_INCLUDE = $(ANTLR_CPP_LIB)
67+
JAR_SPLITTER_TOOL_PATH = $(THIRD_PARTY_PATH)/jar_splitter
6468

6569
INCLUDES = \
6670
-I$(ROOT) \
@@ -91,7 +95,9 @@ INTERNALS_CLASS_LOADER = cdbg_java_agent_internals_loader.class
9195
INTERNALS_CLASS_LOADER_STATIC_DEFS = internals_class_loader_static_defs.inl
9296
INTERNALS_JAR = internals.jar
9397
SERVICE_ACCOUNT_AUTH_TOOL = $(BUILD_TARGET_PATH)/cdbg_service_account_auth.jar
98+
JAR_SPLITTER_TOOL = $(BUILD_TARGET_PATH)/cdbg_jar_splitter.jar
9499
TARGET_TAR_GZ_APPENGINE = $(BUILD_TARGET_PATH)/cdbg_java_agent.tar.gz
100+
TARGET_TAR_GZ_APPENGINE_JAVA8 = $(BUILD_TARGET_PATH)/cdbg_java_agent_gae_java8.tar.gz
95101
TARGET_TAR_GZ_GCE = $(BUILD_TARGET_PATH)/cdbg_java_agent_gce.tar.gz
96102
TARGET_TAR_GZ_SERVICE_ACCOUNT = $(BUILD_TARGET_PATH)/cdbg_java_agent_service_account.tar.gz
97103
TARGET_VERSION_TXT = $(BUILD_TARGET_PATH)/version.txt
@@ -190,6 +196,7 @@ GOOGLE_API_CLIENT_JARS_CLASS_PATH := $(subst $(SPACE),:,$(GOOGLE_API_CLIENT_JARS
190196

191197
all: \
192198
packages \
199+
jar_splitter_tool \
193200
service_account_auth_tool \
194201
target_version_txt \
195202

@@ -255,6 +262,10 @@ else
255262
jar cvf $(INTERNALS_JAR) -C internals/target/ .
256263
endif
257264

265+
jar_splitter_tool:
266+
cd $(JAR_SPLITTER_TOOL_PATH) ; mvn clean install
267+
cp $(JAR_SPLITTER_TOOL_PATH)/target/cdbg_jar_splitter_tool-SNAPSHOT-jar-with-dependencies.jar $(JAR_SPLITTER_TOOL)
268+
258269
# Separate tool to obtain OAuth access token given service account email
259270
# and p12 file. This tool is used by GCE bootstrap script when service account
260271
# authentication is enabled.
@@ -279,9 +290,12 @@ target_version_txt: internals_jar
279290
# (default and recommended solution)
280291
# 3. Java Cloud Debugger package used on GCE with service account authentication
281292
# (Identical to #2, retained for legacy purposes).
293+
# 4. Java Cloud Debugger package used on App Engine Standard Java 8
282294
packages: target_agent internals_jar $(APPENGINE_FORMAT_SCRIPT) target_version_txt
283295
cp $(INTERNALS_JAR) cdbg_java_agent_internals.jar
296+
java -jar $(JAR_SPLITTER_TOOL) cdbg_java_agent_internals.jar .
284297
cp $(TARGET_VERSION_TXT) version.txt
285298
tar cfz $(TARGET_TAR_GZ_APPENGINE) $(TARGET_AGENT) cdbg_java_agent_internals.jar $(APPENGINE_FORMAT_SCRIPT) version.txt
299+
tar cfz $(TARGET_TAR_GZ_APPENGINE_JAVA8) $(TARGET_AGENT) cdbg_java_agent_internals-*.jar version.txt
286300
tar cfz $(TARGET_TAR_GZ_GCE) $(TARGET_AGENT) cdbg_java_agent_internals.jar version.txt
287301
tar cfz $(TARGET_TAR_GZ_SERVICE_ACCOUNT) $(TARGET_AGENT) cdbg_java_agent_internals.jar version.txt

src/agent/internals-class-loader/src/main/java/com/google/devtools/cdbg/debuglets/java/InternalsClassLoader.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@ final class InternalsClassLoader extends URLClassLoader {
3030
/**
3131
* Class constructor.
3232
*
33-
* @param internalsJarPath required path to cdbg_java_agent_internals.jar file
33+
* @param internalsJarPaths required paths to cdbg_java_agent_internals.jar file. This is an array
34+
* as under AppEngine Standard Java 8, the jar file is expected to be split into multiple files to
35+
* work around a maximum file size limit of 32MB.
3436
*/
35-
public InternalsClassLoader(String internalsJarPath) throws MalformedURLException {
36-
super(new URL[] {new URL("file:" + internalsJarPath)}, null); // Parent class loader
37+
public InternalsClassLoader(String[] internalsJarPaths) throws MalformedURLException {
38+
super(convertJarPathsToURLs(internalsJarPaths), null); // Parent class loader
3739
}
3840

3941
@Override
@@ -47,4 +49,13 @@ protected synchronized Class<?> loadClass(String name, boolean resolve)
4749

4850
return super.loadClass(name, resolve);
4951
}
52+
53+
static URL[] convertJarPathsToURLs(String[] internalsJarPath) throws MalformedURLException {
54+
URL[] urls = new URL[internalsJarPath.length];
55+
for (int i = 0; i < urls.length; ++i) {
56+
urls[i] = new URL("file:" + internalsJarPath[i]);
57+
}
58+
59+
return urls;
60+
}
5061
}

src/agent/internals-class-loader/src/test/java/com/google/devtools/cdbg/debuglets/java/InternalsClassLoaderTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public void setUp() throws MalformedURLException {
8181
TestDataPath.get(
8282
"__main__/src/agent/internals-class-loader/src/test/java/com/google/devtools/cdbg/debuglets/java",
8383
"InternalsClassLoaderTest.jar");
84-
classLoader = new InternalsClassLoader(testsJarFile.getAbsolutePath());
84+
classLoader = new InternalsClassLoader(new String[]{testsJarFile.getAbsolutePath()});
8585
}
8686

8787
@Test

src/agent/internals/src/main/java/com/google/devtools/cdbg/debuglets/java/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ java_library(
127127
],
128128
deps = [
129129
":datatype_converter",
130+
":gcp_environment",
130131
":logger",
131132
":safe_caller",
132133
":yaml_config_parser",

src/agent/internals/src/main/java/com/google/devtools/cdbg/debuglets/java/ClassPathLookup.java

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,19 @@ final class ClassPathLookup {
7878
* indexed
7979
* @param extraClassPath optional file system locations to search for .class and .jar files beyond
8080
* what's normally specified in Java class path
81+
* @param agentDir The directory the agent .so file is located.
8182
*/
82-
public ClassPathLookup(boolean useDefaultClassPath, String[] extraClassPath) {
83+
public ClassPathLookup(boolean useDefaultClassPath, String[] extraClassPath, String agentDir) {
8384
infofmt(
84-
"Initializing ClassPathLookup, default classpath: %b, extra classpath: %s",
85-
useDefaultClassPath, (extraClassPath == null) ? "<null>" : Arrays.asList(extraClassPath));
85+
"Initializing ClassPathLookup, default classpath: %b, extra classpath: %s, agent dir: %s",
86+
useDefaultClassPath, (extraClassPath == null) ? "<null>" : Arrays.asList(extraClassPath),
87+
agentDir);
8688

8789
// Try to guess where application classes might be besides class path. We only do it if the
8890
// debugger uses default configuration. If the user sets additional directories, we use that and
8991
// not try to guess anything.
9092
if (useDefaultClassPath && ((extraClassPath == null) || (extraClassPath.length == 0))) {
91-
extraClassPath = findExtraClassPath();
93+
extraClassPath = findExtraClassPath(agentDir);
9294
}
9395

9496
this.useDefaultClassPath = useDefaultClassPath;
@@ -382,10 +384,14 @@ private static InputStream getResource(Class<?> cls) throws ClassNotFoundExcepti
382384
* in the default ROOT directory.
383385
*
384386
* <p>This function is marked as public for unit tests.
387+
*
388+
* @param agentDir The directory the agent .so file is located.
385389
*/
386-
public static String[] findExtraClassPath() {
390+
public static String[] findExtraClassPath(String agentDir) {
387391
Set<String> paths = new HashSet<>();
388392

393+
addAppEngineJava8Paths(paths, agentDir);
394+
389395
// Tomcat.
390396
addSystemPropertyRelative(paths, "catalina.base", "webapps/ROOT/WEB-INF/lib");
391397
addSystemPropertyRelative(paths, "catalina.base", "webapps/ROOT/WEB-INF/classes");
@@ -399,6 +405,23 @@ public static String[] findExtraClassPath() {
399405
return paths.toArray(new String[0]);
400406
}
401407

408+
/**
409+
* If the environment is Java8 App Engine Standard it will attempt to add the USER_DIR/WEB-INF/lib
410+
* and USER_DIR/WEB-INF/classes directories to the paths for indexing.
411+
*
412+
* <p>No effect if the environment is not Java8 App Engine Standard or the directories don't
413+
* exist.
414+
*/
415+
public static void addAppEngineJava8Paths(Set<String> paths, String agentDir) {
416+
String userDir = GcpEnvironment.tryGetAppEngineJava8UserDir(agentDir);
417+
if (userDir == null) {
418+
return;
419+
}
420+
421+
addPathRelative(paths, userDir, "WEB-INF/lib");
422+
addPathRelative(paths, userDir, "WEB-INF/classes");
423+
}
424+
402425
/**
403426
* Appends a path relative to the base path defined in a system property.
404427
*
@@ -410,11 +433,21 @@ private static void addSystemPropertyRelative(Set<String> paths, String name, St
410433
return;
411434
}
412435

413-
File path = new File(value, suffix);
436+
addPathRelative(paths, value, suffix);
437+
}
438+
439+
/**
440+
* Appends a path relative to a base path.
441+
*
442+
* <p>No effect if the combined path does not exist.
443+
*/
444+
private static void addPathRelative(Set<String> paths, String basePath, String suffix) {
445+
File path = new File(basePath, suffix);
414446
if (!path.exists()) {
415447
return;
416448
}
417449

450+
infofmt("Adding path %s for indexing", path.toString());
418451
paths.add(path.toString());
419452
}
420453

src/agent/internals/src/main/java/com/google/devtools/cdbg/debuglets/java/GcpEnvironment.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
import static com.google.devtools.cdbg.debuglets.java.AgentLogger.info;
1717

18+
import java.io.File;
19+
1820
// Needed to wrap the calls to the native logging methods which may fail for unit tests of classes
1921
// that use the AgentLogger, since there won't be a .so loaded that provides the native methods.
2022
import java.lang.UnsatisfiedLinkError;
@@ -173,6 +175,41 @@ static Map<String, String> getDebuggeeLabels() {
173175
return labels;
174176
}
175177

178+
/**
179+
* Returns the user dir of a Java8 App Engine Standard instance. This is the base directory where
180+
* the user's files are deployed. If no Java 8 App Engine Standard user dir could be determined
181+
* null is returned. It is safe to call this regardless of environment, as long as the return
182+
* value is checked.
183+
*
184+
* <p> While there is a system property defined, "user.dir", in Java8 App Engine environments, it
185+
* is unfortunately not set at the point in time this agent code runs, so it cannot be relied
186+
* upon. Here we use the heuristic that the WEB-INF directory is deployed at the root of the user
187+
* dir, and the deployed agent is deployed somewhere under the user dir. This means we can search
188+
* through the agent directory's ancestors looking for the WEB-INFO directory, and once found
189+
* we've found the user dir.
190+
*
191+
* @return the Java 8 App Engine Standard user directory if it was able to be determined, null
192+
* otherwise.
193+
*/
194+
public static String tryGetAppEngineJava8UserDir(String agentDir) {
195+
String gaeRuntime = environmentStore.get("GAE_RUNTIME");
196+
if (gaeRuntime == null || !gaeRuntime.equals("java8")) {
197+
return null;
198+
}
199+
200+
File currentDir = new File(agentDir);
201+
202+
while (currentDir != null) {
203+
if (new File(currentDir.getAbsolutePath(), "WEB-INF").exists()) {
204+
break;
205+
}
206+
207+
currentDir = currentDir.getParentFile();
208+
}
209+
210+
return currentDir == null ? null : currentDir.getAbsolutePath();
211+
}
212+
176213
/**
177214
* Gets the Debuggee.canaryMode from the corresponding System properties.
178215
*/

src/agent/internals/src/test/java/com/google/devtools/cdbg/debuglets/java/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ java_test(
6363
deps = [
6464
":BreakpointTag",
6565
":test_data_path",
66+
"//src/agent/internals/src/main/java/com/google/devtools/cdbg/debuglets/java:gcp_environment",
6667
"//src/agent/internals/src/main/java/com/google/devtools/cdbg/debuglets/java:mapper",
6768
"//tests/agent:libtest_logger.so",
6869
"@maven//:com_google_guava_guava",
@@ -135,6 +136,7 @@ java_test(
135136
"//src/agent/internals/src/main/java/com/google/devtools/cdbg/debuglets/java:labels",
136137
"@maven//:com_google_truth_truth",
137138
"@maven//:junit_junit",
139+
"@maven//:pl_pragmatists_JUnitParams",
138140
],
139141
)
140142

0 commit comments

Comments
 (0)