Skip to content

Commit

Permalink
Modules: Add initial native Chrome code docs
Browse files Browse the repository at this point in the history
Change-Id: Ifd1be95869fa4b3c8bea29fff2e07671abe82e7a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1725189
Commit-Queue: Christopher Grant <cjgrant@chromium.org>
Reviewed-by: Tibor Goldschwendt <tiborg@chromium.org>
Reviewed-by: Andrew Grieve <agrieve@chromium.org>
Cr-Commit-Position: refs/heads/master@{#682858}
  • Loading branch information
Christopher Grant authored and Commit Bot committed Jul 31, 2019
1 parent c220d68 commit 8fea5a1
Show file tree
Hide file tree
Showing 2 changed files with 255 additions and 12 deletions.
214 changes: 202 additions & 12 deletions docs/android_dynamic_feature_modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ Currently (March 2019), DFMs have the following limitations:
you can add your feature to the Android K APK build. See
[crbug/881354](https://bugs.chromium.org/p/chromium/issues/detail?id=881354)
for progress.
* **Native Code:** We cannot move native Chrome code into a DFM. See
[crbug/874564](https://bugs.chromium.org/p/chromium/issues/detail?id=874564)
for progress.

## Getting started

Expand Down Expand Up @@ -316,15 +313,11 @@ flow that tries to executes `bar()`. Depending on whether you installed your
module (`-m foo`) "`bar in module`" or "`module not installed`" is printed to
logcat. Yay!

### Adding third-party native code

### Adding native code

Coming soon (
[crbug/874564](https://bugs.chromium.org/p/chromium/issues/detail?id=874564)).

You can already add third party native code or native Chrome code that has no
dependency on other Chrome code. To add such code add it as a loadable module to
the module descriptor in `//chrome/android/features/foo/foo_module.gni`:
You can add a third-party native library (or any standalone library that doesn't
depend on Chrome code) by adding it as a loadable module to the module descriptor in
`//chrome/android/features/foo/foo_module.gni`:

```gn
foo_module_desc = {
Expand All @@ -334,8 +327,205 @@ foo_module_desc = {
}
```

### Adding Chrome native code

Chrome native code may be placed in a DFM.

A linker-assisted partitioning system automates the placement of code into
either the main Chrome library or feature-specific .so libraries. Feature code
may continue to make use of core Chrome code (eg. base::) without modification,
but Chrome must call feature code through a virtual interface.

Partitioning is explained in [Android Native
Libraries](android_native_libraries.md#partitioned-libraries).

#### Creating an interface to feature code

One way of creating an interface to a feature library is through an interface
definition. Feature Foo could define the following in
`//chrome/android/features/foo/public/foo_interface.h`:

```c++
class FooInterface {
public:
virtual ~FooInterface() = default;

virtual void ProcessInput(const std::string& input) = 0;
}
```
Alongside the interface definition, also in
`//chrome/android/features/foo/public/foo_interface.h`, it's helpful to define a
factory function type that can be used to create a Foo instance:
```c++
typedef FooInterface* CreateFooFunction(bool arg1, bool arg2);
```

<!--- TODO(cjgrant): Add a full, pastable Foo implementation. -->
The feature library implements class Foo, hiding its implementation within the
library. The library may then expose a single entrypoint, a Foo factory
function. Here, C naming is (optionally) used so that the entrypoint symbol
isn't mangled. In `//chrome/android/features/foo/internal/foo.cc`:

```c++
extern "C" {
// This symbol is retrieved from the Foo feature module library via dlsym(),
// where it's bare address is type-cast to its actual type and executed.
// The forward declaration here ensures that CreateFoo()'s signature is correct.
CreateFooFunction CreateFoo;

__attribute__((visibility("default"))) FooInterface* CreateFoo(
bool arg1, bool arg2) {
return new Foo(arg1, arg2);
}
} // extern "C"
```
Ideally, the interface to the feature will avoid feature-specific types. If a
feature defines complex data types, and uses them in its own interface, then its
likely the main library will utilize the code backing these types. That code,
and anything it references, will in turn be pulled back into the main library.
Therefore, designing the feature inferface to use C types, C++ standard types,
or classes that aren't expected to move out of Chrome's main library is ideal.
If feature-specific classes are needed, they simply need to avoid referencing
feature library internals.
*** note
**Note:** To help enforce separation between the feature interface and
implementation, the interface class is best placed in its own GN target, on
which the feature and main library code both depend.
***
#### Marking feature entrypoints
Foo's feature module descriptor needs to pull in the appropriate native GN code
dependencies, and also indicate the name of the file that lists the entrypoint
symbols. In `//chrome/android/features/foo/foo_module.gni`:
```gn
foo_module_desc = {
...
native_deps = [ "//chrome/android/features/foo/internal:foo" ]
native_entrypoints = "//chrome/android/features/foo/internal/module_entrypoints.lst"
}
```

The module entrypoint file is a text file listing symbols. In this example,
`//chrome/android/features/foo/internal/module_entrypoints.lst` has only a
single factory function exposed:

```shell
# This file lists entrypoints exported from the Foo native feature library.

CreateFoo
```

These symbols will be pulled into a version script for the linker, indicating
that they should be exported in the dynamic symbol table of the feature library.

*** note
**Note:** If C++ symbol names are chosen as entrypoints, the full mangled names
must be listed.
***

Additionally, it's necessary to map entrypoints to a particular partition. To
follow compiler/linker convention, this is done at the compiler stage. A cflag
is applied to source file(s) that may supply entrypoints (it's okay to apply the
flag to all feature source - the attribute is utilized only on modules that
export symbols). In `//chrome/android/features/foo/internal/BUILD.gn`:

```gn
static_library("foo") {
sources = [
...
]
# Mark symbols in this target as belonging to the Foo library partition. Only
# exported symbols (entrypoints) are affected, and only if this build supports
# native modules.
if (use_native_modules) {
cflags = [ "-fsymbol-partition=libfoo.so" ]
}
}
```

Feature code is free to use any existing Chrome code (eg. logging, base::,
skia::, cc::, etc), as well as other feature targets. From a GN build config
perspective, the dependencies are defined as they normally would. The
partitioning operation works independently of GN's dependency tree.

```gn
static_library("foo") {
...
# It's fine to depend on base:: and other Chrome code.
deps = [
"//base",
"//cc/animation",
...
]
# Also fine to depend on other feature sub-targets.
deps += [
":some_other_foo_target"
]
# And fine to depend on the interface.
deps += [
":foo_interface"
]
}
```

#### Opening the feature library

Now, code in the main library can open the feature library and create an
instance of feature Foo. Note that in this example, no care is taken to scope
the lifetime of the opened library. Depending on the feature, it may be
preferable to open and close the library as a feature is used.
`//chrome/android/features/foo/factory/foo_factory.cc` may contain this:

```c++
std::unique_ptr<FooInterface> FooFactory(bool arg1, bool arg2) {
// Open the feature library, using the partition library helper to map it into
// the correct memory location. Specifying partition name *foo* will open
// libfoo.so.
void* foo_library_handle =
base::android::BundleUtils::DlOpenModuleLibraryPartition("foo");
}
DCHECK(foo_library_handle != nullptr) << "Could not open foo library:"
<< dlerror();

// Pull the Foo factory function out of the library. The function name isn't
// mangled because it was extern "C".
CreateFooFunction* create_foo = reinterpret_cast<CreateFooFunction*>(
dlsym(foo_library_handle, "CreateFoo"));
DCHECK(create_foo != nullptr);

// Make and return a Foo!
return base::WrapUnique(create_foo(arg1, arg2));
}

```
*** note
**Note:** Component builds do not support partitioned libraries (code splitting
happens across component boundaries instead). As such, an alternate, simplified
feature factory implementation must be supplied (either by linking in a
different factory source file, or using #defines in the factory) that simply
instantiates a Foo object directly.
***
Finally, the main library is free to utilize Foo:
```c++
auto foo = FooFactory::Create(arg1, arg2);
foo->ProcessInput(const std::string& input);
```

### Adding android resources
### Adding Android resources

In this section we will add the required build targets to add Android resources
to the Foo DFM.
Expand Down
53 changes: 53 additions & 0 deletions docs/android_native_libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,59 @@ This doc outlines some tricks / gotchas / features of how we ship native code in
* Similar to O-P, app zygote provides copy-on-write memory semantics so RELRO sharing is redundant.
* For renderer processes, TrichromeWebView works the same way as on Android N-P.

## Partitioned libraries
Some Chrome code is placed in feature-specific libraries and delivered via
[Dynamic Feature Modules](android_dynamic_feature_modules.md).

A linker-assisted partitioning system automates the placement of code into
either the main Chrome library or feature-specific .so libraries. Feature code
may continue to make use of core Chrome code (eg. base::) without modification,
but Chrome must call feature code through a virtual interface.

**How partitioning works**

The lld linker is now capable of producing a [partitioned
library](https://lld.llvm.org/Partitions.html), which is effectively an
intermediate single file containing multiple libraries. A separate tool
*(llvm-objcopy)* then splits the file into standalone .so files, invoked through
a [partitioned shared library](https://cs.chromium.org/chromium/src/build/partitioned_shared_library.gni)
GN template.

The primary partition is Chrome's main library (eg. libchrome.so), and other
partitions may contain feature code (eg. libvr.so). By specifying a list of
C/C++ symbols to use as entrypoints, the linker can collect all code used only
through these entrypoints, and place it in a particular partition.

To facilitate partitioning, all references from Chrome to the feature
entrypoints must be indirect. That is, Chrome must obtain a symbol from the
feature library through dlsym(), cast the pointer to its actual type, and call
through the resulting pointer.

Feature code retains the ability to freely call back into Chrome's core code.
When loading the library, the feature module system uses the feature name to
look up a partition name *(libfoo.so)* in an address offset table built into the
main library. The resulting offset is supplied to android_dlopen_ext(), which
instructs Android to load the library in a particular reserved address region.
This allows the feature library's relative references back to the main library
to work, as if the feature code had been linked into the main library
originally. No dynamic symbol resolution is required here.

**Implications on code placement**

* Any symbol referenced by multiple partitions ends up in the main library (even
if all calling libraries are feature partitions).
* Symbols that aren't feature code (eg. base::) will be pulled into the
feature's library if only that feature uses the code. This is a benefit, but
can be unexpected.

**Builds that support partitioned libraries**

Partitioned libraries are usable when all of the following are true:
* Component build is disabled (component build splits code across GN component
target boundaries instead).
* The compiler is Clang.
* The linker is lld.

## Library Prefetching
* During start-up, we `fork()` a process that reads a byte from each page of the library's memory (or just the ordered range of the library).
* See [//base/android/library_loader/](../base/android/library_loader/).
Expand Down

0 comments on commit 8fea5a1

Please sign in to comment.