1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.content.browser;
7 import android.content.Context;
8 import android.graphics.SurfaceTexture;
9 import android.os.RemoteException;
10 import android.util.Log;
11 import android.util.Pair;
12 import android.view.Surface;
14 import org.chromium.base.CalledByNative;
15 import org.chromium.base.JNINamespace;
16 import org.chromium.base.ThreadUtils;
17 import org.chromium.base.TraceEvent;
18 import org.chromium.base.VisibleForTesting;
19 import org.chromium.base.library_loader.Linker;
20 import org.chromium.content.app.ChildProcessService;
21 import org.chromium.content.app.ChromiumLinkerParams;
22 import org.chromium.content.app.PrivilegedProcessService;
23 import org.chromium.content.app.SandboxedProcessService;
24 import org.chromium.content.common.IChildProcessCallback;
25 import org.chromium.content.common.SurfaceWrapper;
27 import java.util.ArrayList;
29 import java.util.concurrent.ConcurrentHashMap;
32 * This class provides the method to start/stop ChildProcess called by native.
34 @JNINamespace("content")
35 public class ChildProcessLauncher {
36 private static final String TAG = "ChildProcessLauncher";
38 static final int CALLBACK_FOR_UNKNOWN_PROCESS = 0;
39 static final int CALLBACK_FOR_GPU_PROCESS = 1;
40 static final int CALLBACK_FOR_RENDERER_PROCESS = 2;
42 private static final String SWITCH_PROCESS_TYPE = "type";
43 private static final String SWITCH_PPAPI_BROKER_PROCESS = "ppapi-broker";
44 private static final String SWITCH_RENDERER_PROCESS = "renderer";
45 private static final String SWITCH_GPU_PROCESS = "gpu-process";
47 // The upper limit on the number of simultaneous sandboxed and privileged child service process
48 // instances supported. Each limit must not exceed total number of SandboxedProcessServiceX
49 // classes and PrivilegedProcessServiceX classes declared in this package and defined as
50 // services in the embedding application's manifest file.
51 // (See {@link ChildProcessService} for more details on defining the services.)
52 /* package */ static final int MAX_REGISTERED_SANDBOXED_SERVICES = 20;
53 /* package */ static final int MAX_REGISTERED_PRIVILEGED_SERVICES = 3;
55 private static class ChildConnectionAllocator {
56 // Connections to services. Indices of the array correspond to the service numbers.
57 private final ChildProcessConnection[] mChildProcessConnections;
59 // The list of free (not bound) service indices. When looking for a free service, the first
60 // index in that list should be used. When a service is unbound, its index is added to the
61 // end of the list. This is so that we avoid immediately reusing the freed service (see
62 // http://crbug.com/164069): the framework might keep a service process alive when it's been
63 // unbound for a short time. If a new connection to the same service is bound at that point,
64 // the process is reused and bad things happen (mostly static variables are set when we
65 // don't expect them to).
66 // SHOULD BE ACCESSED WITH mConnectionLock.
67 private final ArrayList<Integer> mFreeConnectionIndices;
68 private final Object mConnectionLock = new Object();
70 private Class<? extends ChildProcessService> mChildClass;
71 private final boolean mInSandbox;
73 public ChildConnectionAllocator(boolean inSandbox) {
74 int numChildServices = inSandbox ?
75 MAX_REGISTERED_SANDBOXED_SERVICES : MAX_REGISTERED_PRIVILEGED_SERVICES;
76 mChildProcessConnections = new ChildProcessConnectionImpl[numChildServices];
77 mFreeConnectionIndices = new ArrayList<Integer>(numChildServices);
78 for (int i = 0; i < numChildServices; i++) {
79 mFreeConnectionIndices.add(i);
81 setServiceClass(inSandbox ?
82 SandboxedProcessService.class : PrivilegedProcessService.class);
83 mInSandbox = inSandbox;
86 public void setServiceClass(Class<? extends ChildProcessService> childClass) {
87 mChildClass = childClass;
90 public ChildProcessConnection allocate(
91 Context context, ChildProcessConnection.DeathCallback deathCallback,
92 ChromiumLinkerParams chromiumLinkerParams) {
93 synchronized (mConnectionLock) {
94 if (mFreeConnectionIndices.isEmpty()) {
95 Log.e(TAG, "Ran out of services to allocate.");
99 int slot = mFreeConnectionIndices.remove(0);
100 assert mChildProcessConnections[slot] == null;
101 mChildProcessConnections[slot] = new ChildProcessConnectionImpl(context, slot,
102 mInSandbox, deathCallback, mChildClass, chromiumLinkerParams);
103 Log.d(TAG, "Allocator allocated a connection, sandbox: " + mInSandbox +
105 return mChildProcessConnections[slot];
109 public void free(ChildProcessConnection connection) {
110 synchronized (mConnectionLock) {
111 int slot = connection.getServiceNumber();
112 if (mChildProcessConnections[slot] != connection) {
113 int occupier = mChildProcessConnections[slot] == null ?
114 -1 : mChildProcessConnections[slot].getServiceNumber();
115 Log.e(TAG, "Unable to find connection to free in slot: " + slot +
116 " already occupied by service: " + occupier);
119 mChildProcessConnections[slot] = null;
120 assert !mFreeConnectionIndices.contains(slot);
121 mFreeConnectionIndices.add(slot);
122 Log.d(TAG, "Allocator freed a connection, sandbox: " + mInSandbox +
128 /** @return the count of connections managed by the allocator */
130 int allocatedConnectionsCountForTesting() {
131 return mChildProcessConnections.length - mFreeConnectionIndices.size();
135 // Service class for child process. As the default value it uses SandboxedProcessService0 and
136 // PrivilegedProcessService0.
137 private static final ChildConnectionAllocator sSandboxedChildConnectionAllocator =
138 new ChildConnectionAllocator(true);
139 private static final ChildConnectionAllocator sPrivilegedChildConnectionAllocator =
140 new ChildConnectionAllocator(false);
142 private static boolean sConnectionAllocated = false;
145 * Sets service class for sandboxed service and privileged service.
147 public static void setChildProcessClass(
148 Class<? extends SandboxedProcessService> sandboxedServiceClass,
149 Class<? extends PrivilegedProcessService> privilegedServiceClass) {
150 // We should guarantee this is called before allocating connection.
151 assert !sConnectionAllocated;
152 sSandboxedChildConnectionAllocator.setServiceClass(sandboxedServiceClass);
153 sPrivilegedChildConnectionAllocator.setServiceClass(privilegedServiceClass);
156 private static ChildConnectionAllocator getConnectionAllocator(boolean inSandbox) {
158 sSandboxedChildConnectionAllocator : sPrivilegedChildConnectionAllocator;
161 private static ChildProcessConnection allocateConnection(Context context,
162 boolean inSandbox, ChromiumLinkerParams chromiumLinkerParams) {
163 ChildProcessConnection.DeathCallback deathCallback =
164 new ChildProcessConnection.DeathCallback() {
166 public void onChildProcessDied(ChildProcessConnection connection) {
167 if (connection.getPid() != 0) {
168 stop(connection.getPid());
170 freeConnection(connection);
174 sConnectionAllocated = true;
175 return getConnectionAllocator(inSandbox).allocate(context, deathCallback,
176 chromiumLinkerParams);
179 private static boolean sLinkerInitialized = false;
180 private static long sLinkerLoadAddress = 0;
182 private static ChromiumLinkerParams getLinkerParamsForNewConnection() {
183 if (!sLinkerInitialized) {
184 if (Linker.isUsed()) {
185 sLinkerLoadAddress = Linker.getBaseLoadAddress();
186 if (sLinkerLoadAddress == 0) {
187 Log.i(TAG, "Shared RELRO support disabled!");
190 sLinkerInitialized = true;
193 if (sLinkerLoadAddress == 0)
196 // Always wait for the shared RELROs in service processes.
197 final boolean waitForSharedRelros = true;
198 return new ChromiumLinkerParams(sLinkerLoadAddress,
200 Linker.getTestRunnerClassName());
203 private static ChildProcessConnection allocateBoundConnection(Context context,
204 String[] commandLine, boolean inSandbox) {
205 ChromiumLinkerParams chromiumLinkerParams = getLinkerParamsForNewConnection();
206 ChildProcessConnection connection =
207 allocateConnection(context, inSandbox, chromiumLinkerParams);
208 if (connection != null) {
209 connection.start(commandLine);
214 private static void freeConnection(ChildProcessConnection connection) {
215 getConnectionAllocator(connection.isInSandbox()).free(connection);
218 // Represents an invalid process handle; same as base/process/process.h kNullProcessHandle.
219 private static final int NULL_PROCESS_HANDLE = 0;
221 // Map from pid to ChildService connection.
222 private static Map<Integer, ChildProcessConnection> sServiceMap =
223 new ConcurrentHashMap<Integer, ChildProcessConnection>();
225 // A pre-allocated and pre-bound connection ready for connection setup, or null.
226 private static ChildProcessConnection sSpareSandboxedConnection = null;
228 // Manages oom bindings used to bind chind services.
229 private static BindingManager sBindingManager = BindingManagerImpl.createBindingManager();
231 // Map from surface id to Surface.
232 private static Map<Integer, Surface> sViewSurfaceMap =
233 new ConcurrentHashMap<Integer, Surface>();
235 // Map from surface texture id to Surface.
236 private static Map<Pair<Integer, Integer>, Surface> sSurfaceTextureSurfaceMap =
237 new ConcurrentHashMap<Pair<Integer, Integer>, Surface>();
240 public static void setBindingManagerForTesting(BindingManager manager) {
241 sBindingManager = manager;
244 /** @return true iff the child process is protected from out-of-memory killing */
246 private static boolean isOomProtected(int pid) {
247 return sBindingManager.isOomProtected(pid);
251 private static void registerViewSurface(int surfaceId, Surface surface) {
252 sViewSurfaceMap.put(surfaceId, surface);
256 private static void unregisterViewSurface(int surfaceId) {
257 sViewSurfaceMap.remove(surfaceId);
261 private static void registerSurfaceTexture(
262 int surfaceTextureId, int childProcessId, SurfaceTexture surfaceTexture) {
263 Pair<Integer, Integer> key = new Pair<Integer, Integer>(surfaceTextureId, childProcessId);
264 sSurfaceTextureSurfaceMap.put(key, new Surface(surfaceTexture));
268 private static void unregisterSurfaceTexture(int surfaceTextureId, int childProcessId) {
269 Pair<Integer, Integer> key = new Pair<Integer, Integer>(surfaceTextureId, childProcessId);
270 sSurfaceTextureSurfaceMap.remove(key);
274 * Sets the visibility of the child process when it changes or when it is determined for the
278 public static void setInForeground(int pid, boolean inForeground) {
279 sBindingManager.setInForeground(pid, inForeground);
283 * Called when the embedding application is sent to background.
285 public static void onSentToBackground() {
286 sBindingManager.onSentToBackground();
290 * Called when the embedding application is brought to foreground.
292 public static void onBroughtToForeground() {
293 sBindingManager.onBroughtToForeground();
297 * Should be called early in startup so the work needed to spawn the child process can be done
298 * in parallel to other startup work. Must not be called on the UI thread. Spare connection is
299 * created in sandboxed child process.
300 * @param context the application context used for the connection.
302 public static void warmUp(Context context) {
303 synchronized (ChildProcessLauncher.class) {
304 assert !ThreadUtils.runningOnUiThread();
305 if (sSpareSandboxedConnection == null) {
306 sSpareSandboxedConnection = allocateBoundConnection(context, null, true);
311 private static String getSwitchValue(final String[] commandLine, String switchKey) {
312 if (commandLine == null || switchKey == null) {
315 // This format should be matched with the one defined in command_line.h.
316 final String switchKeyPrefix = "--" + switchKey + "=";
317 for (String command : commandLine) {
318 if (command != null && command.startsWith(switchKeyPrefix)) {
319 return command.substring(switchKeyPrefix.length());
326 * Spawns and connects to a child process. May be called on any thread. It will not block, but
327 * will instead callback to {@link #nativeOnChildProcessStarted} when the connection is
328 * established. Note this callback will not necessarily be from the same thread (currently it
329 * always comes from the main thread).
331 * @param context Context used to obtain the application context.
332 * @param commandLine The child process command line argv.
333 * @param fileIds The ID that should be used when mapping files in the created process.
334 * @param fileFds The file descriptors that should be mapped in the created process.
335 * @param fileAutoClose Whether the file descriptors should be closed once they were passed to
336 * the created process.
337 * @param clientContext Arbitrary parameter used by the client to distinguish this connection.
342 final String[] commandLine,
346 boolean[] fileAutoClose,
347 long clientContext) {
349 assert fileIds.length == fileFds.length && fileFds.length == fileAutoClose.length;
350 FileDescriptorInfo[] filesToBeMapped = new FileDescriptorInfo[fileFds.length];
351 for (int i = 0; i < fileFds.length; i++) {
353 new FileDescriptorInfo(fileIds[i], fileFds[i], fileAutoClose[i]);
355 assert clientContext != 0;
357 int callbackType = CALLBACK_FOR_UNKNOWN_PROCESS;
358 boolean inSandbox = true;
359 String processType = getSwitchValue(commandLine, SWITCH_PROCESS_TYPE);
360 if (SWITCH_RENDERER_PROCESS.equals(processType)) {
361 callbackType = CALLBACK_FOR_RENDERER_PROCESS;
362 } else if (SWITCH_GPU_PROCESS.equals(processType)) {
363 callbackType = CALLBACK_FOR_GPU_PROCESS;
364 } else if (SWITCH_PPAPI_BROKER_PROCESS.equals(processType)) {
368 ChildProcessConnection allocatedConnection = null;
369 synchronized (ChildProcessLauncher.class) {
371 allocatedConnection = sSpareSandboxedConnection;
372 sSpareSandboxedConnection = null;
375 if (allocatedConnection == null) {
376 allocatedConnection = allocateBoundConnection(context, commandLine, inSandbox);
377 if (allocatedConnection == null) {
378 // Notify the native code so it can free the heap allocated callback.
379 nativeOnChildProcessStarted(clientContext, 0);
380 Log.e(TAG, "Allocation of new service failed.");
386 Log.d(TAG, "Setting up connection to process: slot=" +
387 allocatedConnection.getServiceNumber());
388 triggerConnectionSetup(allocatedConnection, commandLine, childProcessId, filesToBeMapped,
389 callbackType, clientContext);
394 static void triggerConnectionSetup(
395 final ChildProcessConnection connection,
396 String[] commandLine,
398 FileDescriptorInfo[] filesToBeMapped,
400 final long clientContext) {
401 ChildProcessConnection.ConnectionCallback connectionCallback =
402 new ChildProcessConnection.ConnectionCallback() {
404 public void onConnected(int pid) {
405 Log.d(TAG, "on connect callback, pid=" + pid + " context=" + clientContext);
406 if (pid != NULL_PROCESS_HANDLE) {
407 sBindingManager.addNewConnection(pid, connection);
408 sServiceMap.put(pid, connection);
410 // If the connection fails and pid == 0, the Java-side cleanup was already
411 // handled by DeathCallback. We still have to call back to native for
413 if (clientContext != 0) { // Will be 0 in Java instrumentation tests.
414 nativeOnChildProcessStarted(clientContext, pid);
419 // TODO(sievers): Revisit this as it doesn't correctly handle the utility process
420 // assert callbackType != CALLBACK_FOR_UNKNOWN_PROCESS;
422 connection.setupConnection(commandLine,
424 createCallback(childProcessId, callbackType),
426 Linker.getSharedRelros());
430 * Terminates a child process. This may be called from any thread.
432 * @param pid The pid (process handle) of the service connection obtained from {@link #start}.
435 static void stop(int pid) {
436 Log.d(TAG, "stopping child connection: pid=" + pid);
437 ChildProcessConnection connection = sServiceMap.remove(pid);
438 if (connection == null) {
439 logPidWarning(pid, "Tried to stop non-existent connection");
442 sBindingManager.clearConnection(pid);
444 freeConnection(connection);
448 * This implementation is used to receive callbacks from the remote service.
450 private static IChildProcessCallback createCallback(
451 final int childProcessId, final int callbackType) {
452 return new IChildProcessCallback.Stub() {
454 * This is called by the remote service regularly to tell us about new values. Note that
455 * IPC calls are dispatched through a thread pool running in each process, so the code
456 * executing here will NOT be running in our main thread -- so, to update the UI, we
457 * need to use a Handler.
460 public void establishSurfacePeer(
461 int pid, Surface surface, int primaryID, int secondaryID) {
462 // Do not allow a malicious renderer to connect to a producer. This is only used
463 // from stream textures managed by the GPU process.
464 if (callbackType != CALLBACK_FOR_GPU_PROCESS) {
465 Log.e(TAG, "Illegal callback for non-GPU process.");
469 nativeEstablishSurfacePeer(pid, surface, primaryID, secondaryID);
473 public SurfaceWrapper getViewSurface(int surfaceId) {
474 // Do not allow a malicious renderer to get to our view surface.
475 if (callbackType != CALLBACK_FOR_GPU_PROCESS) {
476 Log.e(TAG, "Illegal callback for non-GPU process.");
480 Surface surface = sViewSurfaceMap.get(surfaceId);
481 if (surface == null) {
482 Log.e(TAG, "Invalid surfaceId.");
485 assert surface.isValid();
486 return new SurfaceWrapper(surface);
490 public SurfaceWrapper getSurfaceTextureSurface(int primaryId, int secondaryId) {
491 if (callbackType != CALLBACK_FOR_RENDERER_PROCESS) {
492 Log.e(TAG, "Illegal callback for non-renderer process.");
496 if (secondaryId != childProcessId) {
497 Log.e(TAG, "Illegal secondaryId for renderer process.");
501 Pair<Integer, Integer> key = new Pair<Integer, Integer>(primaryId, secondaryId);
502 // Note: This removes the surface and passes the ownership to the caller.
503 Surface surface = sSurfaceTextureSurfaceMap.remove(key);
504 if (surface == null) {
505 Log.e(TAG, "Invalid Id for surface texture.");
508 assert surface.isValid();
509 return new SurfaceWrapper(surface);
514 static void logPidWarning(int pid, String message) {
515 // This class is effectively a no-op in single process mode, so don't log warnings there.
516 if (pid > 0 && !nativeIsSingleProcess()) {
517 Log.w(TAG, message + ", pid=" + pid);
522 static ChildProcessConnection allocateBoundConnectionForTesting(Context context) {
523 return allocateBoundConnection(context, null, true);
526 /** @return the count of sandboxed connections managed by the allocator */
528 static int allocatedConnectionsCountForTesting() {
529 return sSandboxedChildConnectionAllocator.allocatedConnectionsCountForTesting();
532 /** @return the count of services set up and working */
534 static int connectedServicesCountForTesting() {
535 return sServiceMap.size();
539 * Kills the child process for testing.
540 * @return true iff the process was killed as expected
543 public static boolean crashProcessForTesting(int pid) {
544 if (sServiceMap.get(pid) == null) return false;
547 ((ChildProcessConnectionImpl) sServiceMap.get(pid)).crashServiceForTesting();
548 } catch (RemoteException ex) {
555 private static native void nativeOnChildProcessStarted(long clientContext, int pid);
556 private static native void nativeEstablishSurfacePeer(
557 int pid, Surface surface, int primaryID, int secondaryID);
558 private static native boolean nativeIsSingleProcess();