tree: 8f393164f303e002c5a92ec8e8c0bb025b181850 [path history] [tgz]
  1. include/
  2. jni/
  3. .gitignore
  4. MODULE_LICENSE_APACHE2
  5. NOTICE
  6. README.md
  7. test-ndk-build.sh
  8. test-unix-host.sh
samples/solib_rtti_dump/README.md

solib_rtti_dump

A single-file header-file library for debugging RTTI problems with dynamic linking.

It is able to:

  1. Get the address of the std::type_info object for an arbitrary pointer to an object of polymorphic class type.

  2. Dump information about an std::type_info object, including the name of the shared object (or executable) where it is located.

  3. Dump an std::type_info class hierarchy.

  4. Dump information about the current exception from within a catch block, specifically the exception's std::type_info object and the shared object where it is located.

The rtti_dump.h header is a standalone one-file header library. It assumes the use of the “Itanium” C++ ABI (http://itanium-cxx-abi.github.io/cxx-abi/), which is used across many CPU architectures and operating systems. It should work with GCC or Clang and should work with only C++03 language support.

Usage

C++ source: #include "rtti_dump.h"

Link against these libraries: -llog -ldl

On Android, rtti_dump writes to logcat by default. Define RTTI_DUMP_USE_PRINTF to instead use printf.

See jni/demo/upper.cc for example usage. These APIs are defined:

  • const std::type_info *rtti_dump::__cxa_current_exception_type()
  • const std::type_info *rtti_dump::runtime_typeid(const volatile T *dynptr)
  • std::string rtti_dump::dladdr_fname(const void *ptr)
  • void rtti_dump::dump_type(const std::type_info *type, const char *label=..., int indent=...)
  • void rtti_dump::dump_current_exception(const char *label=...)
  • void rtti_dump::dump_class_hierarchy(const std::type_info *info, const char *label=...)

Demo

The demo illustrates problems that occur when an RTTI std::type_info object is duplicated between two dynamic shared objects (DSOs).

Running the demo

The demo expects an armeabi-v7a device running android-16 or newer:

export NDK=$HOME/Android/Sdk/ndk-bundle
export ANDROID_SERIAL=... # if you have multiple devices
./test-ndk-build.sh

Alternatively, it runs on a Unix host:

./test-unix-host.sh

Background

Duplication of the std::type_info objects can break RTTI-based features, including:

  • exception handling
  • dynamic_cast
  • std::type_info::operator==

Frequently, there is no single place to output a type_info object, so the compiler outputs a copy everywhere one is needed, and the copies are later merged. An object like this has “vague linkage”. This deduplication is reliable when multiple object files are linked into a single DSO at build-time. At run-time, copies across multiple DSOs cannot literally be merged, but the system linker can sometimes resolve references to a type_info object, from multiple DSOs, to a single copy from one DSO. This capability is sometimes called “dynamic vague linkage”.

Android's system linker implements dynamic vague linkage, but with two caveats:

  • The target device must be running Android M or later.
  • The deduplication only works among DSOs loaded in a single batch (i.e. in a single call to System.loadLibrary or dlopen).

Code organization

The demo consists of three binaries that resemble a typical Android app that uses JNI:

  • A main executable. Using dlopen, main first loads liblower.so, then libupper.so. The dlopen calls are equivalent to System.loadLibrary calls for the demo's purposes.

  • A liblower.so shared library that defines a few functions.

  • A libupper.so shared library that needs liblower.so and contains the bulk of the test code.

liblower.so and libupper.so contain some duplicated copies of some type_info objects.

The project's Application.mk uses APP_STL := c++_shared, and the main jni/demo test currently fails (as of NDK r16). The test passes with APP_STL := gnustl_shared. libc++/libc++abi assumes that vague linkage is working, whereas gnustl assumes instead that it must use strcmp to decide whether two type_info objects are equal.

The jni/fallback directory demonstrates cases where compiling libc++abi with _LIBCXX_DYNAMIC_FALLBACK is an insufficent fix.

The jni/anon test ignores the DSO issue and instead demonstrates handling of classes in anonymous namespaces. Equality of these classes' type_info must not be determined using strcmp. GCC marks the type_info name with an asterisk prefix to indicate this. The test passes with Clang+libc++ and with GCC+gnustl, but fails with Clang+gnustl (https://bugs.llvm.org/show_bug.cgi?id=34907).

Sample demo output

==== demo_dynamic_cast (jni/demo/upper.cpp,25)
dynamic_cast<Derived*>(lower_ptr): NULL (FAILURE)
*runtime_typeid(lower_ptr) == *runtime_typeid(upper_ptr): false (FAILURE)
lower_type: type N4demo7DerivedE:
lower_type:     type_info obj:  0xf1a4ad20 (in /data/local/tmp/rtti_dump/liblower.so)
lower_type:     type_info name: 0xf1a48f99 (in /data/local/tmp/rtti_dump/liblower.so)
lower_type:     base classes:
lower_type:         type N4demo4BaseE:
lower_type:             type_info obj:  0xf1a4ad18 (in /data/local/tmp/rtti_dump/liblower.so)
lower_type:             type_info name: 0xf1a48fa9 (in /data/local/tmp/rtti_dump/liblower.so)
upper_type: type N4demo7DerivedE:
upper_type:     type_info obj:  0xf1a16ac8 (in /data/local/tmp/rtti_dump/libupper.so)
upper_type:     type_info name: 0xf1a14f06 (in /data/local/tmp/rtti_dump/libupper.so)
upper_type:     base classes:
upper_type:         type N4demo4BaseE:
upper_type:             type_info obj:  0xf1a16ac0 (in /data/local/tmp/rtti_dump/libupper.so)
upper_type:             type_info name: 0xf1a14ef9 (in /data/local/tmp/rtti_dump/libupper.so)

==== demo_catch_string (jni/demo/upper.cpp,54)
typeid(std::string): type NSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE:
typeid(std::string):     type_info obj:  0xf1a16b00 (in /data/local/tmp/rtti_dump/libupper.so)
typeid(std::string):     type_info name: 0xf1a14f20 (in /data/local/tmp/rtti_dump/libupper.so)
current_exception: type NSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE:
current_exception:     type_info obj:  0xf1a4acf0 (in /data/local/tmp/rtti_dump/liblower.so)
current_exception:     type_info name: 0xf1a48f20 (in /data/local/tmp/rtti_dump/liblower.so)
unable to catch std::string object (FAILURE)

==== fallback_do_test (jni/fallback/upper.cpp,58)
dynamic_cast from Src to BaseDup: non-NULL (FAILURE)
dynamic_cast from Src to BaseUniq: NULL (FAILURE)
typeid(BaseDup):  type N8fallback7BaseDupE:
typeid(BaseDup):      type_info obj:  0xf1a16b20 (in /data/local/tmp/rtti_dump/libupper.so)
typeid(BaseDup):      type_info name: 0xf1a14fb0 (in /data/local/tmp/rtti_dump/libupper.so)
typeid(BaseUniq): type N8fallback8BaseUniqE:
typeid(BaseUniq):     type_info obj:  0xf1a16b28 (in /data/local/tmp/rtti_dump/libupper.so)
typeid(BaseUniq):     type_info name: 0xf1a14fd0 (in /data/local/tmp/rtti_dump/libupper.so)
typeid(MidLoc):   type N8fallback6MidLocE:
typeid(MidLoc):       type_info obj:  0xf1a16b30 (in /data/local/tmp/rtti_dump/libupper.so)
typeid(MidLoc):       type_info name: 0xf1a14ff0 (in /data/local/tmp/rtti_dump/libupper.so)
typeid(MidExt):   type N8fallback6MidExtE:
typeid(MidExt):       type_info obj:  0xf1a4ad70 (in /data/local/tmp/rtti_dump/liblower.so)
typeid(MidExt):       type_info name: 0xf1a48fc0 (in /data/local/tmp/rtti_dump/liblower.so)
typeid(Src):      type N8fallback3SrcE:
typeid(Src):          type_info obj:  0xf1a16b18 (in /data/local/tmp/rtti_dump/libupper.so)
typeid(Src):          type_info name: 0xf1a14f99 (in /data/local/tmp/rtti_dump/libupper.so)
typeid(Full) hierarchy: type N8fallback4FullE:
typeid(Full) hierarchy:     type_info obj:  0xf1a16b40 (in /data/local/tmp/rtti_dump/libupper.so)
typeid(Full) hierarchy:     type_info name: 0xf1a15010 (in /data/local/tmp/rtti_dump/libupper.so)
typeid(Full) hierarchy:     base classes:
typeid(Full) hierarchy:         type N8fallback6MidLocE:
typeid(Full) hierarchy:             type_info obj:  0xf1a16b30 (in /data/local/tmp/rtti_dump/libupper.so)
typeid(Full) hierarchy:             type_info name: 0xf1a14ff0 (in /data/local/tmp/rtti_dump/libupper.so)
typeid(Full) hierarchy:             base classes:
typeid(Full) hierarchy:                 type N8fallback7BaseDupE:
typeid(Full) hierarchy:                     type_info obj:  0xf1a16b20 (in /data/local/tmp/rtti_dump/libupper.so)
typeid(Full) hierarchy:                     type_info name: 0xf1a14fb0 (in /data/local/tmp/rtti_dump/libupper.so)
typeid(Full) hierarchy:         type N8fallback6MidExtE:
typeid(Full) hierarchy:             type_info obj:  0xf1a4ad70 (in /data/local/tmp/rtti_dump/liblower.so)
typeid(Full) hierarchy:             type_info name: 0xf1a48fc0 (in /data/local/tmp/rtti_dump/liblower.so)
typeid(Full) hierarchy:             base classes:
typeid(Full) hierarchy:                 type N8fallback7BaseDupE:
typeid(Full) hierarchy:                     type_info obj:  0xf1a4ad90 (in /data/local/tmp/rtti_dump/liblower.so)
typeid(Full) hierarchy:                     type_info name: 0xf1a48fe0 (in /data/local/tmp/rtti_dump/liblower.so)
typeid(Full) hierarchy:                 type N8fallback8BaseUniqE:
typeid(Full) hierarchy:                     type_info obj:  0xf1a4ad98 (in /data/local/tmp/rtti_dump/liblower.so)
typeid(Full) hierarchy:                     type_info name: 0xf1a49000 (in /data/local/tmp/rtti_dump/liblower.so)
typeid(Full) hierarchy:         type N8fallback3SrcE:
typeid(Full) hierarchy:             type_info obj:  0xf1a16b18 (in /data/local/tmp/rtti_dump/libupper.so)
typeid(Full) hierarchy:             type_info name: 0xf1a14f99 (in /data/local/tmp/rtti_dump/libupper.so)

==== anon_do_test (jni/anon/upper2.cpp,34)
dynamic_cast of upper1:Anon from Base* to upper2:Anon*: NULL (PASS)
typeid(upper1:Anon) == typeid(upper2:Anon): false (PASS)
upper1: typeid(Anon): type N4anon12_GLOBAL__N_14AnonE:
upper1: typeid(Anon):     type_info obj:  0xf1a16bf4 (in /data/local/tmp/rtti_dump/libupper.so)
upper1: typeid(Anon):     type_info name: 0xf1a15030 (in /data/local/tmp/rtti_dump/libupper.so)
upper1: typeid(Anon):     base classes:
upper1: typeid(Anon):         type N4anon4BaseE:
upper1: typeid(Anon):             type_info obj:  0xf1a16bec (in /data/local/tmp/rtti_dump/libupper.so)
upper1: typeid(Anon):             type_info name: 0xf1a1504b (in /data/local/tmp/rtti_dump/libupper.so)
upper2: typeid(Anon): type N4anon12_GLOBAL__N_14AnonE:
upper2: typeid(Anon):     type_info obj:  0xf1a16c20 (in /data/local/tmp/rtti_dump/libupper.so)
upper2: typeid(Anon):     type_info name: 0xf1a15060 (in /data/local/tmp/rtti_dump/libupper.so)
upper2: typeid(Anon):     base classes:
upper2: typeid(Anon):         type N4anon4BaseE:
upper2: typeid(Anon):             type_info obj:  0xf1a16bec (in /data/local/tmp/rtti_dump/libupper.so)
upper2: typeid(Anon):             type_info name: 0xf1a1504b (in /data/local/tmp/rtti_dump/libupper.so)

License

Copyright (C) 2017 The Android Open Source Project

Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.