From 181f3fd789edc374a2709d777f32b56fc8be446b Mon Sep 17 00:00:00 2001 From: Logan Chien Date: Wed, 11 Jan 2017 10:54:08 +0800 Subject: [PATCH] Add VNDK definition tool. This is the binary-based VNDK definition tool. Test: python -m unittest discover -s tests -p 'test_*.py' Change-Id: I66ccba2864bf19548bfcec0e277957e3370fe4bd --- vndk/tools/definition-tool/README.md | 157 ++++ .../tests/expected/arm/libtest-rpath.so.txt | 9 + .../tests/expected/arm/libtest-runpath.so.txt | 9 + .../tests/expected/arm/libtest.so.txt | 8 + .../tests/expected/arm/main.out.txt | 9 + .../tests/expected/arm64/libtest-rpath.so.txt | 13 + .../expected/arm64/libtest-runpath.so.txt | 13 + .../tests/expected/arm64/libtest.so.txt | 12 + .../tests/expected/arm64/main.out.txt | 17 + .../tests/expected/mips/libtest-rpath.so.txt | 16 + .../expected/mips/libtest-runpath.so.txt | 16 + .../tests/expected/mips/libtest.so.txt | 15 + .../tests/expected/mips/main.out.txt | 23 + .../expected/mips64/libtest-rpath.so.txt | 15 + .../expected/mips64/libtest-runpath.so.txt | 15 + .../tests/expected/mips64/libtest.so.txt | 14 + .../tests/expected/mips64/main.out.txt | 21 + .../tests/expected/x86/libtest-rpath.so.txt | 9 + .../tests/expected/x86/libtest-runpath.so.txt | 9 + .../tests/expected/x86/libtest.so.txt | 8 + .../tests/expected/x86/main.out.txt | 9 + .../expected/x86_64/libtest-rpath.so.txt | 9 + .../expected/x86_64/libtest-runpath.so.txt | 9 + .../tests/expected/x86_64/libtest.so.txt | 8 + .../tests/expected/x86_64/main.out.txt | 9 + vndk/tools/definition-tool/tests/input/main.c | 25 + vndk/tools/definition-tool/tests/input/test.c | 7 + vndk/tools/definition-tool/tests/targets.py | 121 +++ .../definition-tool/tests/test_elfdump.py | 163 ++++ .../definition-tool/vndk_definition_tool.py | 878 ++++++++++++++++++ 30 files changed, 1646 insertions(+) create mode 100644 vndk/tools/definition-tool/README.md create mode 100644 vndk/tools/definition-tool/tests/expected/arm/libtest-rpath.so.txt create mode 100644 vndk/tools/definition-tool/tests/expected/arm/libtest-runpath.so.txt create mode 100644 vndk/tools/definition-tool/tests/expected/arm/libtest.so.txt create mode 100644 vndk/tools/definition-tool/tests/expected/arm/main.out.txt create mode 100644 vndk/tools/definition-tool/tests/expected/arm64/libtest-rpath.so.txt create mode 100644 vndk/tools/definition-tool/tests/expected/arm64/libtest-runpath.so.txt create mode 100644 vndk/tools/definition-tool/tests/expected/arm64/libtest.so.txt create mode 100644 vndk/tools/definition-tool/tests/expected/arm64/main.out.txt create mode 100644 vndk/tools/definition-tool/tests/expected/mips/libtest-rpath.so.txt create mode 100644 vndk/tools/definition-tool/tests/expected/mips/libtest-runpath.so.txt create mode 100644 vndk/tools/definition-tool/tests/expected/mips/libtest.so.txt create mode 100644 vndk/tools/definition-tool/tests/expected/mips/main.out.txt create mode 100644 vndk/tools/definition-tool/tests/expected/mips64/libtest-rpath.so.txt create mode 100644 vndk/tools/definition-tool/tests/expected/mips64/libtest-runpath.so.txt create mode 100644 vndk/tools/definition-tool/tests/expected/mips64/libtest.so.txt create mode 100644 vndk/tools/definition-tool/tests/expected/mips64/main.out.txt create mode 100644 vndk/tools/definition-tool/tests/expected/x86/libtest-rpath.so.txt create mode 100644 vndk/tools/definition-tool/tests/expected/x86/libtest-runpath.so.txt create mode 100644 vndk/tools/definition-tool/tests/expected/x86/libtest.so.txt create mode 100644 vndk/tools/definition-tool/tests/expected/x86/main.out.txt create mode 100644 vndk/tools/definition-tool/tests/expected/x86_64/libtest-rpath.so.txt create mode 100644 vndk/tools/definition-tool/tests/expected/x86_64/libtest-runpath.so.txt create mode 100644 vndk/tools/definition-tool/tests/expected/x86_64/libtest.so.txt create mode 100644 vndk/tools/definition-tool/tests/expected/x86_64/main.out.txt create mode 100644 vndk/tools/definition-tool/tests/input/main.c create mode 100644 vndk/tools/definition-tool/tests/input/test.c create mode 100644 vndk/tools/definition-tool/tests/targets.py create mode 100755 vndk/tools/definition-tool/tests/test_elfdump.py create mode 100755 vndk/tools/definition-tool/vndk_definition_tool.py diff --git a/vndk/tools/definition-tool/README.md b/vndk/tools/definition-tool/README.md new file mode 100644 index 000000000..df6317a69 --- /dev/null +++ b/vndk/tools/definition-tool/README.md @@ -0,0 +1,157 @@ +VNDK Definition Tool +==================== + +## Usage + +Create a generic reference symbols from AOSP build: + + $ python3 vndk_definition_tool.py create-generic-ref \ + -o generic_arm64/system \ + ${OUT_DIR_COMMON_BASE}/target/product/generic_arm64/system + +Run the VNDK definition tool with: + + $ python3 vndk_definition_tool.py vndk \ + --system ${ANDROID_PRODUCT_OUT}/system \ + --vendor ${ANDROID_PRODUCT_OUT}/vendor \ + --load-generic-refs generic_arm64 + +This command will give you three different lists: + +- vndk-core: VNDK libraries that are both used by frameworks and HALs. This + set will be installed on system partition. + +- vndk-indirect: VNDK libraries that are indirectly used by vndk-core but + not directly used by vendors. This set will be installed on system partition. + +- vndk-ext: VNDK libraries that are both used by frameworks and HALs. This set + is for the libraries that do not have an functionally-equivalent AOSP + counterpart. This set will be installed on vendor partition. + + +# Sub Directory Tagging + +If there are some sub directory under system partition must be treated as +vendor files, then specify such directory with: `--system-dir-as-vendor`. + +Conversely, if there are some sub directory under vendor partition must be +treated as system files, then specify such directory with: +`--vendor-dir-as-system`. + +For example, if the device does not have an independent `vendor` partition (but +with a `vendor` folder in the `system` partition), then run this command: + + $ python3 vndk_definition_tool.py vndk \ + --system ${ANDROID_PRODUCT_OUT}/system \ + --system-dir-as-vendor vendor \ + --load-generic-refs generic_arm64 + +For example, if `/system/bin/hw`, `/system/lib/hw`, and `/system/lib64/hw` are +containing vendor files, then run this command: + + $ python3 vndk_definition_tool.py vndk \ + --system ${ANDROID_PRODUCT_OUT}/system \ + --system-dir-as-vendor bin/hw \ + --system-dir-as-vendor lib/hw \ + --system-dir-as-vendor lib64/hw \ + --vendor ${ANDROID_PRODUCT_OUT}/vendor \ + --load-generic-refs generic_arm64 + + +## Implicit Dependencies + +If there are implicit dependencies, such as `dlopen()`, we can specify them in +a dependency file and load the dependency file with `--load-extra-deps`. The +dependency file format is simple: (a) each line stands for a dependency, and +(b) the file before the colon depends on the file after the colon. For +example, `libart.so` depends on `libart-compiler.so`: + + /system/lib64/libart.so: /system/lib64/libart-compiler.so + +And then, run VNDK definition tool with: + + $ python3 vndk_definition_tool.py vndk \ + --system ${ANDROID_PRODUCT_OUT}/system \ + --vendor ${ANDROID_PRODUCT_OUT}/vendor \ + --load-generic-refs generic_arm64 \ + --load-extra-deps dlopen.dep + + +## Warnings + +### Incorrect Partition + +If you specify `--warn-incorrect-partition` command line option, then VNDK +definition tool will emit warnings when: + +1. A framework library is only used by vendor binaries. + +2. A vendor library is only used by framework binaries. + +This allows people to review the correct partition for the module. For example, + + warning: /system/lib/libtinyxml.so: This is a framework library with + vendor-only usages. + + warning: /system/lib64/libtinyxml.so: This is a framework library with + vendor-only usages. + +These warnings suggest that `libtinyxml.so` might be better to move to vendor +partition. + + +### VNDK Extension + +If you specify `--load-generic-refs`, then you may see some warnings: + + warning: /system/lib/libhardware_legacy.so: This is a VNDK extension and + must be moved to vendor partition. + +This warning indicates that the library is not in the generic reference or the +library contains some symbols that are not available in generic build. It must +be installed into vendor partition instead. As the result, it will be included +in vndk-ext. + + +## Example + +We can run this against Nexus 6p Factory Image: + + $ unzip angler-nmf26f-factory-ef607244.zip + + $ cd angler-nmf26f + + $ unzip image-angler-nmf26f.zip + + $ simg2img system.img system.raw.img + + $ simg2img vendor.img vendor.raw.img + + $ mkdir system + + $ mkdir vendor + + $ sudo mount -o loop,ro system.raw.img system + + $ sudo mount -o loop,ro vendor.raw.img vendor + + $ sudo python3 vndk_definition_tool.py vndk \ + --system system --vendor vendor + +We can run this against latest Android build: + + $ python3 vndk_definition_tool.py vndk \ + --system ${ANDROID_PRODUCT_OUT}/system \ + --system-dir-as-vendor bin/hw \ + --system-dir-as-vendor lib/hw \ + --system-dir-as-vendor lib64/hw \ + --vendor ${ANDROID_PRODUCT_OUT}/vendor + + +## Python 2 Support + +Since `vndk_definition_tool.py` runs 3x faster with Python 3, the shebang is +specifying `python3` by default. To run `vndk_definition_tool.py` with +python2, run the following command: + + $ python vndk_definition_tool.py [options] diff --git a/vndk/tools/definition-tool/tests/expected/arm/libtest-rpath.so.txt b/vndk/tools/definition-tool/tests/expected/arm/libtest-rpath.so.txt new file mode 100644 index 000000000..bd73f72aa --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/arm/libtest-rpath.so.txt @@ -0,0 +1,9 @@ +EI_CLASS 32 +EI_DATA Little-Endian +E_MACHINE EM_ARM +DT_RPATH $ORIGIN/../lib +DT_NEEDED libc.so +SYMBOL __bss_start +SYMBOL _edata +SYMBOL _end +SYMBOL test diff --git a/vndk/tools/definition-tool/tests/expected/arm/libtest-runpath.so.txt b/vndk/tools/definition-tool/tests/expected/arm/libtest-runpath.so.txt new file mode 100644 index 000000000..d60d172d8 --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/arm/libtest-runpath.so.txt @@ -0,0 +1,9 @@ +EI_CLASS 32 +EI_DATA Little-Endian +E_MACHINE EM_ARM +DT_RUNPATH $ORIGIN/../lib +DT_NEEDED libc.so +SYMBOL __bss_start +SYMBOL _edata +SYMBOL _end +SYMBOL test diff --git a/vndk/tools/definition-tool/tests/expected/arm/libtest.so.txt b/vndk/tools/definition-tool/tests/expected/arm/libtest.so.txt new file mode 100644 index 000000000..a04399437 --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/arm/libtest.so.txt @@ -0,0 +1,8 @@ +EI_CLASS 32 +EI_DATA Little-Endian +E_MACHINE EM_ARM +DT_NEEDED libc.so +SYMBOL __bss_start +SYMBOL _edata +SYMBOL _end +SYMBOL test diff --git a/vndk/tools/definition-tool/tests/expected/arm/main.out.txt b/vndk/tools/definition-tool/tests/expected/arm/main.out.txt new file mode 100644 index 000000000..10f9d0593 --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/arm/main.out.txt @@ -0,0 +1,9 @@ +EI_CLASS 32 +EI_DATA Little-Endian +E_MACHINE EM_ARM +DT_NEEDED libdl.so +DT_NEEDED libc.so +DT_NEEDED libstdc++.so +SYMBOL __bss_start +SYMBOL _edata +SYMBOL _end diff --git a/vndk/tools/definition-tool/tests/expected/arm64/libtest-rpath.so.txt b/vndk/tools/definition-tool/tests/expected/arm64/libtest-rpath.so.txt new file mode 100644 index 000000000..72ed3da5b --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/arm64/libtest-rpath.so.txt @@ -0,0 +1,13 @@ +EI_CLASS 64 +EI_DATA Little-Endian +E_MACHINE EM_AARCH64 +DT_RPATH $ORIGIN/../lib +DT_NEEDED libc.so +SYMBOL __bss_end__ +SYMBOL __bss_start +SYMBOL __bss_start__ +SYMBOL __end__ +SYMBOL _bss_end__ +SYMBOL _edata +SYMBOL _end +SYMBOL test diff --git a/vndk/tools/definition-tool/tests/expected/arm64/libtest-runpath.so.txt b/vndk/tools/definition-tool/tests/expected/arm64/libtest-runpath.so.txt new file mode 100644 index 000000000..0754c3237 --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/arm64/libtest-runpath.so.txt @@ -0,0 +1,13 @@ +EI_CLASS 64 +EI_DATA Little-Endian +E_MACHINE EM_AARCH64 +DT_RUNPATH $ORIGIN/../lib +DT_NEEDED libc.so +SYMBOL __bss_end__ +SYMBOL __bss_start +SYMBOL __bss_start__ +SYMBOL __end__ +SYMBOL _bss_end__ +SYMBOL _edata +SYMBOL _end +SYMBOL test diff --git a/vndk/tools/definition-tool/tests/expected/arm64/libtest.so.txt b/vndk/tools/definition-tool/tests/expected/arm64/libtest.so.txt new file mode 100644 index 000000000..7f00e9f40 --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/arm64/libtest.so.txt @@ -0,0 +1,12 @@ +EI_CLASS 64 +EI_DATA Little-Endian +E_MACHINE EM_AARCH64 +DT_NEEDED libc.so +SYMBOL __bss_end__ +SYMBOL __bss_start +SYMBOL __bss_start__ +SYMBOL __end__ +SYMBOL _bss_end__ +SYMBOL _edata +SYMBOL _end +SYMBOL test diff --git a/vndk/tools/definition-tool/tests/expected/arm64/main.out.txt b/vndk/tools/definition-tool/tests/expected/arm64/main.out.txt new file mode 100644 index 000000000..57691c121 --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/arm64/main.out.txt @@ -0,0 +1,17 @@ +EI_CLASS 64 +EI_DATA Little-Endian +E_MACHINE EM_AARCH64 +DT_NEEDED libdl.so +DT_NEEDED libc.so +DT_NEEDED libstdc++.so +SYMBOL __FINI_ARRAY__ +SYMBOL __INIT_ARRAY__ +SYMBOL __PREINIT_ARRAY__ +SYMBOL __bss_end__ +SYMBOL __bss_start +SYMBOL __bss_start__ +SYMBOL __end__ +SYMBOL _bss_end__ +SYMBOL _edata +SYMBOL _end +SYMBOL main diff --git a/vndk/tools/definition-tool/tests/expected/mips/libtest-rpath.so.txt b/vndk/tools/definition-tool/tests/expected/mips/libtest-rpath.so.txt new file mode 100644 index 000000000..647d4198b --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/mips/libtest-rpath.so.txt @@ -0,0 +1,16 @@ +EI_CLASS 32 +EI_DATA Little-Endian +E_MACHINE EM_MIPS +DT_RPATH $ORIGIN/../lib +DT_NEEDED libc.so +SYMBOL __bss_end__ +SYMBOL __bss_start +SYMBOL __end__ +SYMBOL _bss_end__ +SYMBOL _edata +SYMBOL _end +SYMBOL _fbss +SYMBOL _fdata +SYMBOL _ftext +SYMBOL _gp_disp +SYMBOL test diff --git a/vndk/tools/definition-tool/tests/expected/mips/libtest-runpath.so.txt b/vndk/tools/definition-tool/tests/expected/mips/libtest-runpath.so.txt new file mode 100644 index 000000000..15a93cd75 --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/mips/libtest-runpath.so.txt @@ -0,0 +1,16 @@ +EI_CLASS 32 +EI_DATA Little-Endian +E_MACHINE EM_MIPS +DT_RUNPATH $ORIGIN/../lib +DT_NEEDED libc.so +SYMBOL __bss_end__ +SYMBOL __bss_start +SYMBOL __end__ +SYMBOL _bss_end__ +SYMBOL _edata +SYMBOL _end +SYMBOL _fbss +SYMBOL _fdata +SYMBOL _ftext +SYMBOL _gp_disp +SYMBOL test diff --git a/vndk/tools/definition-tool/tests/expected/mips/libtest.so.txt b/vndk/tools/definition-tool/tests/expected/mips/libtest.so.txt new file mode 100644 index 000000000..97fa2ce50 --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/mips/libtest.so.txt @@ -0,0 +1,15 @@ +EI_CLASS 32 +EI_DATA Little-Endian +E_MACHINE EM_MIPS +DT_NEEDED libc.so +SYMBOL __bss_end__ +SYMBOL __bss_start +SYMBOL __end__ +SYMBOL _bss_end__ +SYMBOL _edata +SYMBOL _end +SYMBOL _fbss +SYMBOL _fdata +SYMBOL _ftext +SYMBOL _gp_disp +SYMBOL test diff --git a/vndk/tools/definition-tool/tests/expected/mips/main.out.txt b/vndk/tools/definition-tool/tests/expected/mips/main.out.txt new file mode 100644 index 000000000..3261684d2 --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/mips/main.out.txt @@ -0,0 +1,23 @@ +EI_CLASS 32 +EI_DATA Little-Endian +E_MACHINE EM_MIPS +DT_NEEDED libdl.so +DT_NEEDED libc.so +DT_NEEDED libstdc++.so +SYMBOL _DYNAMIC_LINKING +SYMBOL __CTOR_LIST__ +SYMBOL __DTOR_LIST__ +SYMBOL __FINI_ARRAY__ +SYMBOL __INIT_ARRAY__ +SYMBOL __PREINIT_ARRAY__ +SYMBOL __RLD_MAP +SYMBOL __bss_end__ +SYMBOL __bss_start +SYMBOL __end__ +SYMBOL _bss_end__ +SYMBOL _edata +SYMBOL _end +SYMBOL _fbss +SYMBOL _fdata +SYMBOL _ftext +SYMBOL main diff --git a/vndk/tools/definition-tool/tests/expected/mips64/libtest-rpath.so.txt b/vndk/tools/definition-tool/tests/expected/mips64/libtest-rpath.so.txt new file mode 100644 index 000000000..7a69f1649 --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/mips64/libtest-rpath.so.txt @@ -0,0 +1,15 @@ +EI_CLASS 64 +EI_DATA Little-Endian +E_MACHINE EM_MIPS +DT_RPATH $ORIGIN/../lib +DT_NEEDED libc.so +SYMBOL __bss_end__ +SYMBOL __bss_start +SYMBOL __end__ +SYMBOL _bss_end__ +SYMBOL _edata +SYMBOL _end +SYMBOL _fbss +SYMBOL _fdata +SYMBOL _ftext +SYMBOL test diff --git a/vndk/tools/definition-tool/tests/expected/mips64/libtest-runpath.so.txt b/vndk/tools/definition-tool/tests/expected/mips64/libtest-runpath.so.txt new file mode 100644 index 000000000..cbe0d4489 --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/mips64/libtest-runpath.so.txt @@ -0,0 +1,15 @@ +EI_CLASS 64 +EI_DATA Little-Endian +E_MACHINE EM_MIPS +DT_RUNPATH $ORIGIN/../lib +DT_NEEDED libc.so +SYMBOL __bss_end__ +SYMBOL __bss_start +SYMBOL __end__ +SYMBOL _bss_end__ +SYMBOL _edata +SYMBOL _end +SYMBOL _fbss +SYMBOL _fdata +SYMBOL _ftext +SYMBOL test diff --git a/vndk/tools/definition-tool/tests/expected/mips64/libtest.so.txt b/vndk/tools/definition-tool/tests/expected/mips64/libtest.so.txt new file mode 100644 index 000000000..270e60d1e --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/mips64/libtest.so.txt @@ -0,0 +1,14 @@ +EI_CLASS 64 +EI_DATA Little-Endian +E_MACHINE EM_MIPS +DT_NEEDED libc.so +SYMBOL __bss_end__ +SYMBOL __bss_start +SYMBOL __end__ +SYMBOL _bss_end__ +SYMBOL _edata +SYMBOL _end +SYMBOL _fbss +SYMBOL _fdata +SYMBOL _ftext +SYMBOL test diff --git a/vndk/tools/definition-tool/tests/expected/mips64/main.out.txt b/vndk/tools/definition-tool/tests/expected/mips64/main.out.txt new file mode 100644 index 000000000..7e7150c11 --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/mips64/main.out.txt @@ -0,0 +1,21 @@ +EI_CLASS 64 +EI_DATA Little-Endian +E_MACHINE EM_MIPS +DT_NEEDED libdl.so +DT_NEEDED libc.so +DT_NEEDED libstdc++.so +SYMBOL _DYNAMIC_LINKING +SYMBOL __FINI_ARRAY__ +SYMBOL __INIT_ARRAY__ +SYMBOL __PREINIT_ARRAY__ +SYMBOL __RLD_MAP +SYMBOL __bss_end__ +SYMBOL __bss_start +SYMBOL __end__ +SYMBOL _bss_end__ +SYMBOL _edata +SYMBOL _end +SYMBOL _fbss +SYMBOL _fdata +SYMBOL _ftext +SYMBOL main diff --git a/vndk/tools/definition-tool/tests/expected/x86/libtest-rpath.so.txt b/vndk/tools/definition-tool/tests/expected/x86/libtest-rpath.so.txt new file mode 100644 index 000000000..bb9088226 --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/x86/libtest-rpath.so.txt @@ -0,0 +1,9 @@ +EI_CLASS 32 +EI_DATA Little-Endian +E_MACHINE EM_386 +DT_RPATH $ORIGIN/../lib +DT_NEEDED libc.so +SYMBOL __bss_start +SYMBOL _edata +SYMBOL _end +SYMBOL test diff --git a/vndk/tools/definition-tool/tests/expected/x86/libtest-runpath.so.txt b/vndk/tools/definition-tool/tests/expected/x86/libtest-runpath.so.txt new file mode 100644 index 000000000..55c338e23 --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/x86/libtest-runpath.so.txt @@ -0,0 +1,9 @@ +EI_CLASS 32 +EI_DATA Little-Endian +E_MACHINE EM_386 +DT_RUNPATH $ORIGIN/../lib +DT_NEEDED libc.so +SYMBOL __bss_start +SYMBOL _edata +SYMBOL _end +SYMBOL test diff --git a/vndk/tools/definition-tool/tests/expected/x86/libtest.so.txt b/vndk/tools/definition-tool/tests/expected/x86/libtest.so.txt new file mode 100644 index 000000000..cf337583b --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/x86/libtest.so.txt @@ -0,0 +1,8 @@ +EI_CLASS 32 +EI_DATA Little-Endian +E_MACHINE EM_386 +DT_NEEDED libc.so +SYMBOL __bss_start +SYMBOL _edata +SYMBOL _end +SYMBOL test diff --git a/vndk/tools/definition-tool/tests/expected/x86/main.out.txt b/vndk/tools/definition-tool/tests/expected/x86/main.out.txt new file mode 100644 index 000000000..45c33b89a --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/x86/main.out.txt @@ -0,0 +1,9 @@ +EI_CLASS 32 +EI_DATA Little-Endian +E_MACHINE EM_386 +DT_NEEDED libdl.so +DT_NEEDED libc.so +DT_NEEDED libstdc++.so +SYMBOL __bss_start +SYMBOL _edata +SYMBOL _end diff --git a/vndk/tools/definition-tool/tests/expected/x86_64/libtest-rpath.so.txt b/vndk/tools/definition-tool/tests/expected/x86_64/libtest-rpath.so.txt new file mode 100644 index 000000000..2ec92a156 --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/x86_64/libtest-rpath.so.txt @@ -0,0 +1,9 @@ +EI_CLASS 64 +EI_DATA Little-Endian +E_MACHINE EM_X86_64 +DT_RPATH $ORIGIN/../lib +DT_NEEDED libc.so +SYMBOL __bss_start +SYMBOL _edata +SYMBOL _end +SYMBOL test diff --git a/vndk/tools/definition-tool/tests/expected/x86_64/libtest-runpath.so.txt b/vndk/tools/definition-tool/tests/expected/x86_64/libtest-runpath.so.txt new file mode 100644 index 000000000..ebdf49045 --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/x86_64/libtest-runpath.so.txt @@ -0,0 +1,9 @@ +EI_CLASS 64 +EI_DATA Little-Endian +E_MACHINE EM_X86_64 +DT_RUNPATH $ORIGIN/../lib +DT_NEEDED libc.so +SYMBOL __bss_start +SYMBOL _edata +SYMBOL _end +SYMBOL test diff --git a/vndk/tools/definition-tool/tests/expected/x86_64/libtest.so.txt b/vndk/tools/definition-tool/tests/expected/x86_64/libtest.so.txt new file mode 100644 index 000000000..62dc31a8a --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/x86_64/libtest.so.txt @@ -0,0 +1,8 @@ +EI_CLASS 64 +EI_DATA Little-Endian +E_MACHINE EM_X86_64 +DT_NEEDED libc.so +SYMBOL __bss_start +SYMBOL _edata +SYMBOL _end +SYMBOL test diff --git a/vndk/tools/definition-tool/tests/expected/x86_64/main.out.txt b/vndk/tools/definition-tool/tests/expected/x86_64/main.out.txt new file mode 100644 index 000000000..888a4cf74 --- /dev/null +++ b/vndk/tools/definition-tool/tests/expected/x86_64/main.out.txt @@ -0,0 +1,9 @@ +EI_CLASS 64 +EI_DATA Little-Endian +E_MACHINE EM_X86_64 +DT_NEEDED libdl.so +DT_NEEDED libc.so +DT_NEEDED libstdc++.so +SYMBOL __bss_start +SYMBOL _edata +SYMBOL _end diff --git a/vndk/tools/definition-tool/tests/input/main.c b/vndk/tools/definition-tool/tests/input/main.c new file mode 100644 index 000000000..ec98faa94 --- /dev/null +++ b/vndk/tools/definition-tool/tests/input/main.c @@ -0,0 +1,25 @@ +#include +#include + +int main(int argc, char **argv) { + if (argc < 2) { + puts("usage: main.out libtest.so"); + return 1; + } + + void *handle = dlopen(argv[1], RTLD_NOW); + if (!handle) { + puts("failed to open lib"); + return 1; + } + + void (*test)(void) = dlsym(handle, "test"); + if (!test) { + puts("failed to find test() function"); + } else { + test(); + } + + dlclose(handle); + return 0; +} diff --git a/vndk/tools/definition-tool/tests/input/test.c b/vndk/tools/definition-tool/tests/input/test.c new file mode 100644 index 000000000..2a9b2702e --- /dev/null +++ b/vndk/tools/definition-tool/tests/input/test.c @@ -0,0 +1,7 @@ +extern void test(); + +extern int puts(const char *); + +void test() { + puts("hello world"); +} diff --git a/vndk/tools/definition-tool/tests/targets.py b/vndk/tools/definition-tool/tests/targets.py new file mode 100644 index 000000000..2b2d2ac54 --- /dev/null +++ b/vndk/tools/definition-tool/tests/targets.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python + +import os +import subprocess +import sys + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +ANDROID_BUILD_TOP = os.path.abspath(os.path.join(SCRIPT_DIR, *(['..'] * 5))) + +NDK_VERSION = 'r11' +API_LEVEL = 'android-24' + +def get_prebuilts_host(): + if sys.platform.startswith('linux'): + return 'linux-x86' + if sys.platform.startswith('darwin'): + return 'darwin-x86' + raise NotImplementedError('unknown platform') + +def get_prebuilts_gcc(arch, gcc_version): + return os.path.join(ANDROID_BUILD_TOP, 'prebuilts', 'gcc', + get_prebuilts_host(), arch, gcc_version) + +def get_prebuilts_clang(): + return os.path.join(ANDROID_BUILD_TOP, 'prebuilts', 'clang', 'host', + get_prebuilts_host(), 'clang-stable') + +def get_prebuilts_ndk(subdirs): + return os.path.join(ANDROID_BUILD_TOP, 'prebuilts', 'ndk', NDK_VERSION, + 'platforms', API_LEVEL, *subdirs) + + +class Target(object): + def __init__(self, name, triple, cflags, ldflags, gcc_toolchain_dir, + clang_dir, ndk_include, ndk_lib): + self.name = name + self.target_triple = triple + self.target_cflags = cflags + self.target_ldflags = ldflags + + self.gcc_toolchain_dir = gcc_toolchain_dir + self.clang_dir = clang_dir + self.ndk_include = ndk_include + self.ndk_lib = ndk_lib + + def compile(self, obj_file, src_file, cflags): + clang = os.path.join(self.clang_dir, 'bin', 'clang') + + cmd = [clang, '-o', obj_file, '-c', src_file] + cmd.extend(['-fPIE', '-fPIC']) + cmd.extend(['-gcc-toolchain', self.gcc_toolchain_dir]) + cmd.extend(['-target', self.target_triple]) + cmd.extend(['-isystem', self.ndk_include]) + cmd.extend(cflags) + cmd.extend(self.target_cflags) + subprocess.check_call(cmd) + + def link(self, out_file, obj_files, ldflags): + if '-shared' in ldflags: + crtbegin = os.path.join(self.ndk_lib, 'crtbegin_so.o') + crtend = os.path.join(self.ndk_lib, 'crtend_so.o') + else: + crtbegin = os.path.join(self.ndk_lib, 'crtbegin_static.o') + crtend = os.path.join(self.ndk_lib, 'crtend_android.o') + + clang = os.path.join(self.clang_dir, 'bin', 'clang') + + cmd = [clang, '-o', out_file] + cmd.extend(['-fPIE', '-fPIC', '-Wl,--no-undefined', '-nostdlib']) + cmd.append('-L' + self.ndk_lib) + cmd.extend(['-gcc-toolchain', self.gcc_toolchain_dir]) + cmd.extend(['-target', self.target_triple]) + cmd.append(crtbegin) + cmd.extend(obj_files) + cmd.append(crtend) + cmd.extend(ldflags) + cmd.extend(self.target_ldflags) + if '-shared' not in ldflags: + cmd.append('-Wl,-pie') + subprocess.check_call(cmd) + +def create_targets(): + return { + 'arm': Target('arm', 'arm-linux-androideabi', [],[], + get_prebuilts_gcc('arm', 'arm-linux-androideabi-4.9'), + get_prebuilts_clang(), + get_prebuilts_ndk(['arch-arm', 'usr', 'include']), + get_prebuilts_ndk(['arch-arm', 'usr', 'lib'])), + + 'arm64': Target('arm64', 'aarch64-linux-android', [], [], + get_prebuilts_gcc('aarch64', 'aarch64-linux-android-4.9'), + get_prebuilts_clang(), + get_prebuilts_ndk(['arch-arm64', 'usr', 'include']), + get_prebuilts_ndk(['arch-arm64', 'usr', 'lib'])), + + 'x86': Target('x86', 'x86_64-linux-android', ['-m32'], ['-m32'], + get_prebuilts_gcc('x86', 'x86_64-linux-android-4.9'), + get_prebuilts_clang(), + get_prebuilts_ndk(['arch-x86', 'usr', 'include']), + get_prebuilts_ndk(['arch-x86', 'usr', 'lib'])), + + 'x86_64': Target('x86_64', 'x86_64-linux-android', ['-m64'], ['-m64'], + get_prebuilts_gcc('x86', 'x86_64-linux-android-4.9'), + get_prebuilts_clang(), + get_prebuilts_ndk(['arch-x86_64', 'usr', 'include']), + get_prebuilts_ndk(['arch-x86_64', 'usr', 'lib64'])), + + 'mips': Target('mips', 'mipsel-linux-android', [], [], + get_prebuilts_gcc('mips', 'mips64el-linux-android-4.9'), + get_prebuilts_clang(), + get_prebuilts_ndk(['arch-mips', 'usr', 'include']), + get_prebuilts_ndk(['arch-mips', 'usr', 'lib'])), + + 'mips64': Target('mips64', 'mips64el-linux-android', + ['-march=mips64el', '-mcpu=mips64r6'], + ['-march=mips64el', '-mcpu=mips64r6'], + get_prebuilts_gcc('mips', 'mips64el-linux-android-4.9'), + get_prebuilts_clang(), + get_prebuilts_ndk(['arch-mips64', 'usr', 'include']), + get_prebuilts_ndk(['arch-mips64', 'usr', 'lib64'])), + } diff --git a/vndk/tools/definition-tool/tests/test_elfdump.py b/vndk/tools/definition-tool/tests/test_elfdump.py new file mode 100755 index 000000000..f71cd1766 --- /dev/null +++ b/vndk/tools/definition-tool/tests/test_elfdump.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 + +from __future__ import print_function + +import argparse +import collections +import difflib +import os +import subprocess +import sys +import unittest + +import targets + +try: + from tempfile import TemporaryDirectory +except ImportError: + import shutil + import tempfile + + class TemporaryDirectory(object): + def __init__(self, suffix='', prefix='tmp', dir=None): + self.name = tempfile.mkdtemp(suffix, prefix, dir) + + def __del__(self): + self.cleanup() + + def __enter__(self): + return self.name + + def __exit__(self, exc, value, tb): + self.cleanup() + + def cleanup(self): + if self.name: + shutil.rmtree(self.name) + self.name = None + +if sys.version_info >= (3, 0): + from os import makedirs +else: + def makedirs(path, exist_ok): + if exist_ok and os.path.exists(path): + return + return os.makedirs(path) + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +VNDK_DEF_TOOL = os.path.join(SCRIPT_DIR, '..', 'vndk_definition_tool.py') + +expected_dir = os.path.join(SCRIPT_DIR, 'expected') +test_dir_base = None + +def run_elf_dump(path): + cmd = [sys.executable, VNDK_DEF_TOOL, 'elfdump', path] + return subprocess.check_output(cmd, universal_newlines=True) + + +class ELFDumpTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.targets = targets.create_targets() + + def setUp(self): + if test_dir_base: + self.test_dir_base = test_dir_base + else: + self.tmp_dir = TemporaryDirectory() + self.test_dir_base = self.tmp_dir.name + + def tearDown(self): + if not test_dir_base: + self.tmp_dir.cleanup() + + def _prepare_dir(self, target_name): + self.expected_dir = os.path.join(expected_dir, target_name) + self.test_dir = os.path.join(self.test_dir_base, target_name) + makedirs(self.test_dir, exist_ok=True) + + def _assert_equal_to_file(self, expected_file_name, actual): + actual = actual.splitlines(True) + expected_file_path = os.path.join(self.expected_dir, expected_file_name) + with open(expected_file_path, 'r') as f: + expected = f.readlines() + self.assertEqual(expected, actual) + + def _test_main_out(self, target): + self._prepare_dir(target.name) + + src_file = os.path.join(SCRIPT_DIR, 'input', 'main.c') + obj_file = os.path.join(self.test_dir, 'main.o') + target.compile(obj_file, src_file, []) + + out_file = os.path.join(self.test_dir, 'main.out') + target.link(out_file, [obj_file], ['-ldl', '-lc', '-lstdc++']) + self._assert_equal_to_file('main.out.txt', run_elf_dump(out_file)) + + def _test_libtest(self, target, ldflags, output_name, expected_file_name): + self._prepare_dir(target.name) + + src_file = os.path.join(SCRIPT_DIR, 'input', 'test.c') + obj_file = os.path.join(self.test_dir, 'test.o') + target.compile(obj_file, src_file, []) + + out_file = os.path.join(self.test_dir, output_name) + target.link(out_file, [obj_file], ['-shared', '-lc'] + ldflags) + self._assert_equal_to_file(expected_file_name, run_elf_dump(out_file)) + + +def create_target_test(target_name): + def test_main(self): + self._test_main_out(self.targets[target_name]) + + def test_libtest(self): + self._test_libtest( + self.targets[target_name], [], 'libtest.so', 'libtest.so.txt') + + def test_libtest_rpath(self): + self._test_libtest( + self.targets[target_name], ['-Wl,-rpath,$ORIGIN/../lib'], + 'libtest-rpath.so', 'libtest-rpath.so.txt') + + def test_libtest_runpath(self): + self._test_libtest( + self.targets[target_name], + ['-Wl,-rpath,$ORIGIN/../lib', '-Wl,--enable-new-dtags'], + 'libtest-runpath.so', 'libtest-runpath.so.txt') + + class_name = 'ELFDumpTest_' + target_name + globals()[class_name] = type( + class_name, (ELFDumpTest,), + dict(test_main=test_main, + test_libtest=test_libtest, + test_libtest_rpath=test_libtest_rpath, + test_libtest_runpath=test_libtest_runpath)) + + +for target in ('arm', 'arm64', 'mips', 'mips64', 'x86', 'x86_64'): + create_target_test(target) + + +def main(): + # Parse command line arguments. + parser = argparse.ArgumentParser() + parser.add_argument('--test-dir', + help='directory for temporary files') + parser.add_argument('--expected-dir', help='directory with expected output') + args, unittest_args = parser.parse_known_args() + + # Convert command line options. + global expected_dir + global test_dir_base + + if args.expected_dir: + expected_dir = args.expected_dir + if args.test_dir: + test_dir_base = args.test_dir + makedirs(test_dir_base, exist_ok=True) + + # Run unit test. + unittest.main(argv=[sys.argv[0]] + unittest_args) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/vndk/tools/definition-tool/vndk_definition_tool.py b/vndk/tools/definition-tool/vndk_definition_tool.py new file mode 100755 index 000000000..650671f9f --- /dev/null +++ b/vndk/tools/definition-tool/vndk_definition_tool.py @@ -0,0 +1,878 @@ +#!/usr/bin/env python3 + +from __future__ import print_function + +import argparse +import collections +import mmap +import os +import re +import stat +import struct +import sys + + +if sys.version_info >= (3, 0): + from os import makedirs + from mmap import ACCESS_READ, mmap +else: + from mmap import ACCESS_READ, mmap + + def makedirs(path, exist_ok): + if exist_ok and os.path.isdir(path): + return + return os.makedirs(path) + + class mmap(mmap): + def __enter__(self): + return self + + def __exit__(self, exc, value, tb): + self.close() + + def __getitem__(self, key): + res = super(mmap, self).__getitem__(key) + if type(key) == int: + return ord(res) + return res + + FileNotFoundError = OSError + + +try: + from sys import intern +except ImportError: + pass + + +EI_CLASS = 4 +EI_DATA = 5 + +ELFCLASSNONE = 0 +ELFCLASS32 = 1 +ELFCLASS64 = 2 + +ELFDATANONE = 0 +ELFDATA2LSB = 1 +ELFDATA2MSB = 2 + +DT_NEEDED = 1 +DT_RPATH = 15 +DT_RUNPATH = 29 + +SHN_UNDEF = 0 + +STB_LOCAL = 0 +STB_GLOBAL = 1 +STB_WEAK = 2 + + +Elf_Hdr = collections.namedtuple( + 'Elf_Hdr', + 'ei_class ei_data ei_version ei_osabi e_type e_machine e_version ' + + 'e_entry e_phoff e_shoff e_flags e_ehsize e_phentsize e_phnum ' + + 'e_shentsize e_shnum e_shstridx') + + +Elf_Shdr = collections.namedtuple( + 'Elf_Shdr', + 'sh_name sh_type sh_flags sh_addr sh_offset sh_size sh_link sh_info ' + + 'sh_addralign sh_entsize') + + +Elf_Dyn = collections.namedtuple('Elf_Dyn', 'd_tag d_val') + + +class Elf_Sym(object): + __slots__ = ( + 'st_name', 'st_value', 'st_size', 'st_info', 'st_other', 'st_shndx' + ) + + def __init__(self, st_name, st_value, st_size, st_info, st_other, st_shndx): + self.st_name = st_name + self.st_value = st_value + self.st_size = st_size + self.st_info = st_info + self.st_other = st_other + self.st_shndx = st_shndx + + def __str__(self): + return ('Elf_Sym(' + + 'st_name=' + repr(self.st_name) + ', ' + 'st_value=' + repr(self.st_value) + ', ' + 'st_size=' + repr(self.st_size) + ', ' + 'st_info=' + repr(self.st_info) + ', ' + 'st_other=' + repr(self.st_other) + ', ' + 'st_shndx=' + repr(self.st_shndx) + ')') + + @staticmethod + def _make(p): + return Elf_Sym(*p) + + @property + def st_bind(self): + return (self.st_info >> 4) + + +def _get_elf_class_name(ei_class): + if ei_class == ELFCLASS32: + return '32' + if ei_class == ELFCLASS64: + return '64' + return 'None' + + +def _get_elf_data_name(ei_data): + if ei_data == ELFDATA2LSB: + return 'Little-Endian' + if ei_data == ELFDATA2MSB: + return 'Big-Endian' + return 'None' + + +_ELF_MACHINE_ID_TABLE = { + 0: 'EM_NONE', + 3: 'EM_386', + 8: 'EM_MIPS', + 40: 'EM_ARM', + 62: 'EM_X86_64', + 183: 'EM_AARCH64', +} + + +def _get_elf_machine_name(e_machine): + return _ELF_MACHINE_ID_TABLE.get(e_machine, str(e_machine)) + + +def _extract_zero_end_slice(buf, offset): + end = offset + try: + while buf[end] != 0: + end += 1 + except IndexError: + pass + return buf[offset:end] + + +if sys.version_info >= (3, 0): + def _extract_zero_end_str(buf, offset): + return intern(_extract_zero_end_slice(buf, offset).decode('utf-8')) +else: + def _extract_zero_end_str(buf, offset): + return intern(_extract_zero_end_slice(buf, offset)) + + +class ELFError(ValueError): + pass + + +class ELF(object): + def __init__(self, ei_class=ELFCLASSNONE, ei_data=ELFDATANONE, e_machine=0, + dt_rpath=None, dt_runpath=None, dt_needed=None, + exported_symbols=None): + self.ei_class = ei_class + self.ei_data = ei_data + self.e_machine = e_machine + self.dt_rpath = dt_rpath + self.dt_runpath = dt_runpath + self.dt_needed = dt_needed if dt_needed is not None else [] + self.exported_symbols = \ + exported_symbols if exported_symbols is not None else [] + + def __str__(self): + return ('ELF(' + + 'ei_class=' + repr(self.ei_class) + ', ' + + 'ei_data=' + repr(self.ei_data) + ', ' + + 'e_machine=' + repr(self.e_machine) + ', ' + + 'dt_rpath=' + repr(self.dt_rpath) + ', ' + + 'dt_runpath=' + repr(self.dt_runpath) + ', ' + + 'dt_needed=' + repr(self.dt_needed) + ')') + + def dump(self, file=None): + file = file if file is not None else sys.stdout + + print('EI_CLASS\t' + _get_elf_class_name(self.ei_class), file=file) + print('EI_DATA\t\t' + _get_elf_data_name(self.ei_data), file=file) + print('E_MACHINE\t' + _get_elf_machine_name(self.e_machine), file=file) + if self.dt_rpath: + print('DT_RPATH\t' + self.dt_rpath, file=file) + if self.dt_runpath: + print('DT_RUNPATH\t' + self.dt_runpath, file=file) + for dt_needed in self.dt_needed: + print('DT_NEEDED\t' + dt_needed, file=file) + for symbol in self.exported_symbols: + print('SYMBOL\t\t' + symbol, file=file) + + def dump_exported_symbols(self, file=None): + file = file if file is not None else sys.stdout + + for symbol in self.exported_symbols: + print(symbol, file=file) + + def _parse_from_buf_internal(self, buf): + # Check ELF ident. + if buf.size() < 8: + raise ELFError('bad ident') + + if buf[0:4] != b'\x7fELF': + raise ELFError('bad magic') + + self.ei_class = buf[EI_CLASS] + if self.ei_class != ELFCLASS32 and self.ei_class != ELFCLASS64: + raise ELFError('unknown word size') + + self.ei_data = buf[EI_DATA] + if self.ei_data != ELFDATA2LSB and self.ei_data != ELFDATA2MSB: + raise ELFError('unknown endianness') + + # ELF structure definitions. + endian_fmt = '<' if self.ei_data == ELFDATA2LSB else '>' + + if self.ei_class == ELFCLASS32: + elf_hdr_fmt = endian_fmt + '4x4B8xHHLLLLLHHHHHH' + elf_shdr_fmt = endian_fmt + 'LLLLLLLLLL' + elf_dyn_fmt = endian_fmt + 'lL' + elf_sym_fmt = endian_fmt + 'LLLBBH' + else: + elf_hdr_fmt = endian_fmt + '4x4B8xHHLQQQLHHHHHH' + elf_shdr_fmt = endian_fmt + 'LLQQQQLLQQ' + elf_dyn_fmt = endian_fmt + 'QQ' + elf_sym_fmt = endian_fmt + 'LBBHQQ' + + def parse_struct(cls, fmt, offset, error_msg): + try: + return cls._make(struct.unpack_from(fmt, buf, offset)) + except struct.error: + raise ELFError(error_msg) + + def parse_elf_hdr(offset): + return parse_struct(Elf_Hdr, elf_hdr_fmt, offset, 'bad elf header') + + def parse_elf_shdr(offset): + return parse_struct(Elf_Shdr, elf_shdr_fmt, offset, + 'bad section header') + + def parse_elf_dyn(offset): + return parse_struct(Elf_Dyn, elf_dyn_fmt, offset, + 'bad .dynamic entry') + + if self.ei_class == ELFCLASS32: + def parse_elf_sym(offset): + return parse_struct(Elf_Sym, elf_sym_fmt, offset, 'bad elf sym') + else: + def parse_elf_sym(offset): + try: + p = struct.unpack_from(elf_sym_fmt, buf, offset) + return Elf_Sym(p[0], p[4], p[5], p[1], p[2], p[3]) + except struct.error: + raise ELFError('bad elf sym') + + def extract_str(offset): + return _extract_zero_end_str(buf, offset) + + # Parse ELF header. + header = parse_elf_hdr(0) + self.e_machine = header.e_machine + + # Check section header size. + if header.e_shentsize == 0: + raise ELFError('no section header') + + # Find .shstrtab section. + shstrtab_shdr_off = \ + header.e_shoff + header.e_shstridx * header.e_shentsize + shstrtab_shdr = parse_elf_shdr(shstrtab_shdr_off) + shstrtab_off = shstrtab_shdr.sh_offset + + # Parse ELF section header. + sections = dict() + header_end = header.e_shoff + header.e_shnum * header.e_shentsize + for shdr_off in range(header.e_shoff, header_end, header.e_shentsize): + shdr = parse_elf_shdr(shdr_off) + name = extract_str(shstrtab_off + shdr.sh_name) + sections[name] = shdr + + # Find .dynamic and .dynstr section header. + dynamic_shdr = sections.get('.dynamic') + if not dynamic_shdr: + raise ELFError('no .dynamic section') + + dynstr_shdr = sections.get('.dynstr') + if not dynstr_shdr: + raise ELFError('no .dynstr section') + + dynamic_off = dynamic_shdr.sh_offset + dynstr_off = dynstr_shdr.sh_offset + + # Parse entries in .dynamic section. + assert struct.calcsize(elf_dyn_fmt) == dynamic_shdr.sh_entsize + dynamic_end = dynamic_off + dynamic_shdr.sh_size + for ent_off in range(dynamic_off, dynamic_end, dynamic_shdr.sh_entsize): + ent = parse_elf_dyn(ent_off) + if ent.d_tag == DT_NEEDED: + self.dt_needed.append(extract_str(dynstr_off + ent.d_val)) + elif ent.d_tag == DT_RPATH: + self.dt_rpath = extract_str(dynstr_off + ent.d_val) + elif ent.d_tag == DT_RUNPATH: + self.dt_runpath = extract_str(dynstr_off + ent.d_val) + + # Parse exported symbols in .dynsym section. + dynsym_shdr = sections.get('.dynsym') + if dynsym_shdr: + exported_symbols = [] + dynsym_off = dynsym_shdr.sh_offset + dynsym_end = dynsym_off + dynsym_shdr.sh_size + dynsym_entsize = dynsym_shdr.sh_entsize + for ent_off in range(dynsym_off, dynsym_end, dynsym_entsize): + ent = parse_elf_sym(ent_off) + if ent.st_bind != STB_LOCAL and ent.st_shndx != SHN_UNDEF: + exported_symbols.append( + extract_str(dynstr_off + ent.st_name)) + exported_symbols.sort() + self.exported_symbols = exported_symbols + + def _parse_from_buf(self, buf): + try: + self._parse_from_buf_internal(buf) + except IndexError: + raise ELFError('bad offset') + + def _parse_from_file(self, path): + with open(path, 'rb') as f: + st = os.fstat(f.fileno()) + if not st.st_size: + raise ELFError('empty file') + with mmap(f.fileno(), st.st_size, access=ACCESS_READ) as image: + self._parse_from_buf(image) + + @staticmethod + def load(path): + elf = ELF() + elf._parse_from_file(path) + return elf + + @staticmethod + def loads(buf): + elf = ELF() + elf._parse_from_buf(buf) + return elf + + +PT_SYSTEM = 0 +PT_VENDOR = 1 +NUM_PARTITIONS = 2 + + +NDK_LOW_LEVEL = { + 'libc.so', 'libstdc++.so', 'libdl.so', 'liblog.so', 'libm.so', 'libz.so', +} + + +NDK_HIGH_LEVEL = { + 'libandroid.so', 'libcamera2ndk.so', 'libEGL.so', 'libGLESv1_CM.so', + 'libGLESv2.so', 'libGLESv3.so', 'libjnigraphics.so', 'libmediandk.so', + 'libOpenMAXAL.so', 'libOpenSLES.so', 'libvulkan.so', +} + +def _is_ndk_lib(path): + lib_name = os.path.basename(path) + return lib_name in NDK_LOW_LEVEL or lib_name in NDK_HIGH_LEVEL + + +BANNED_LIBS = { + 'libbinder.so', +} + + +def is_accessible(path): + try: + mode = os.stat(path).st_mode + return (mode & (stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)) != 0 + except FileNotFoundError: + return False + + +def scan_executables(root): + for base, dirs, files in os.walk(root): + for filename in files: + path = os.path.join(base, filename) + if is_accessible(path): + yield path + + +class GraphNode(object): + def __init__(self, partition, path, elf): + self.partition = partition + self.path = path + self.elf = elf + self.deps = set() + self.users = set() + self.is_ndk = _is_ndk_lib(path) + + def add_dep(self, dst): + self.deps.add(dst) + dst.users.add(self) + + +def sorted_lib_path_list(libs): + libs = [lib.path for lib in libs] + libs.sort() + return libs + + +class Graph(object): + def __init__(self): + self.lib32 = dict() + self.lib64 = dict() + self.lib_pt = [dict() for i in range(NUM_PARTITIONS)] + + def add(self, partition, path, elf): + node = GraphNode(partition, path, elf) + if elf.ei_class == ELFCLASS32: + self.lib32[path] = node + else: + self.lib64[path] = node + self.lib_pt[partition][path] = node + + def add_dep(self, src_path, dst_path): + for lib_set in (self.lib32, self.lib64): + src = lib_set.get(src_path) + dst = lib_set.get(dst_path) + if src and dst: + src.add_dep(dst) + + @staticmethod + def _compile_path_matcher(root, subdirs): + dirs = [os.path.normpath(os.path.join(root, i)) for i in subdirs] + patts = ['(?:' + re.escape(i) + ')' for i in dirs] + return re.compile('|'.join(patts)) + + def add_executables_in_dir(self, partition_name, partition, root, + alter_partition, alter_subdirs): + root = os.path.abspath(root) + prefix_len = len(root) + 1 + + if alter_subdirs: + alter_patt = Graph._compile_path_matcher(root, alter_subdirs) + + for path in scan_executables(root): + try: + elf = ELF.load(path) + except ELFError as e: + continue + + short_path = os.path.join('/', partition_name, path[prefix_len:]) + if alter_subdirs and alter_patt.match(path): + self.add(alter_partition, short_path, elf) + else: + self.add(partition, short_path, elf) + + def load_extra_deps(self, path): + patt = re.compile('([^:]*):\\s*(.*)') + with open(path, 'r') as f: + for line in f: + match = patt.match(line) + if match: + self.add_dep(match.group(1), match.group(2)) + + def _resolve_deps_lib_set(self, lib_set, system_lib, vendor_lib): + for lib in lib_set.values(): + for dt_needed in lib.elf.dt_needed: + candidates = [ + dt_needed, + os.path.join(system_lib, dt_needed), + os.path.join(vendor_lib, dt_needed), + ] + for candidate in candidates: + dep = lib_set.get(candidate) + if dep: + break + if not dep: + print('warning: {}: Missing needed library: {} Tried: {}' + .format(lib.path, dt_needed, candidates), + file=sys.stderr) + continue + lib.add_dep(dep) + + def resolve_deps(self): + self._resolve_deps_lib_set(self.lib32, '/system/lib', '/vendor/lib') + self._resolve_deps_lib_set(self.lib64, '/system/lib64', + '/vendor/lib64') + + def compute_vndk_libs(self, generic_refs): + vndk_core = set() + vndk_ext = set() + + def collect_lib_with_partition_user(result, lib_set, partition): + for lib in lib_set.values(): + for user in lib.users: + if user.partition == partition: + result.add(lib) + break + + # Check library usages from vendor to system. + collect_lib_with_partition_user( + vndk_core, self.lib_pt[PT_SYSTEM], PT_VENDOR) + + # Check library usages from system to vendor. + collect_lib_with_partition_user( + vndk_ext, self.lib_pt[PT_VENDOR], PT_SYSTEM) + + # Remove NDK libraries. + def remove_ndk_libs(libs): + return set(lib for lib in libs if not lib.is_ndk) + + vndk_core = remove_ndk_libs(vndk_core) + vndk_ext = remove_ndk_libs(vndk_ext) + + # Compute transitive closure. + def get_transitive_closure(root, boundary): + closure = set(root) + stack = list(root) + while stack: + lib = stack.pop() + for dep in lib.deps: + if dep.is_ndk: + continue + if dep not in closure and dep not in boundary: + closure.add(dep) + stack.append(dep) + return closure + + vndk_indirect = get_transitive_closure(vndk_core, vndk_ext) - vndk_core + vndk_ext = get_transitive_closure(vndk_ext, vndk_core) + + # Move extended libraries from vndk_core to vndk_ext. + if generic_refs: + stack = list(vndk_core) + stacked = vndk_core + vndk_core = set() + + while stack: + lib = stack.pop() + if generic_refs.is_equivalent_lib(lib): + vndk_core.add(lib) + continue + + print('warning: {}: This is a VNDK extension and must be ' + 'moved to vendor partition.'.format(lib.path), + file=sys.stderr) + + # Move the library from vndk_core to vndk_ext. + vndk_ext.add(lib) + for dep in lib.deps: + # Skip all NDK dependencies. They are not VNDK. + if dep.is_ndk: + continue + # Skip vndk_ext and possibly vndk_core. + if dep in vndk_ext or dep in stacked: + continue + # Promote the dependency from vndk_indirect to vndk_core. + assert dep in vndk_indirect + vndk_indirect.remove(dep) + stack.append(dep) + stacked.add(dep) + + return (vndk_core, vndk_indirect, vndk_ext) + + @staticmethod + def create(system_dirs=None, system_dirs_as_vendor=None, vendor_dirs=None, + vendor_dirs_as_system=None, extra_deps=None): + graph = Graph() + + if system_dirs: + for path in system_dirs: + graph.add_executables_in_dir('system', PT_SYSTEM, path, + PT_VENDOR, system_dirs_as_vendor) + + if vendor_dirs: + for path in vendor_dirs: + graph.add_executables_in_dir('vendor', PT_VENDOR, path, + PT_SYSTEM, vendor_dirs_as_system) + + if extra_deps: + for path in extra_deps: + graph.load_extra_deps(path) + + graph.resolve_deps() + + return graph + + +class GenericRefs(object): + def __init__(self): + self.refs = dict() + + def _load_from_dir(self, root): + root = os.path.abspath(root) + prefix_len = len(root) + 1 + for base, dirnames, filenames in os.walk(root): + for filename in filenames: + if not filename.endswith('.sym'): + continue + path = os.path.join(base, filename) + lib_name = '/' + path[prefix_len:-4] + with open(path, 'r') as f: + self.refs[lib_name] = [line.strip() for line in f] + + @staticmethod + def create_from_dir(root): + result = GenericRefs() + result._load_from_dir(root) + return result + + def is_equivalent_lib(self, lib): + return self.refs.get(lib.path) == lib.elf.exported_symbols + + +class Command(object): + def __init__(self, name, help): + self.name = name + self.help = help + + def add_argparser_options(self, parser): + pass + + def main(self, args): + return 0 + + +class ELFDumpCommand(Command): + def __init__(self): + super(ELFDumpCommand, self).__init__( + 'elfdump', help='Dump ELF .dynamic section') + + def add_argparser_options(self, parser): + parser.add_argument('path', help='path to an ELF file') + + def main(self, args): + try: + ELF.load(args.path).dump() + except ELFError as e: + print('error: {}: Bad ELF file ({})'.format(args.path, e), + file=sys.stderr) + sys.exit(1) + return 0 + + +class CreateGenericRefCommand(Command): + def __init__(self): + super(CreateGenericRefCommand, self).__init__( + 'create-generic-ref', help='Create generic references') + + def add_argparser_options(self, parser): + parser.add_argument('dir') + + parser.add_argument( + '--output', '-o', metavar='PATH', required=True, + help='output directory') + + def main(self, args): + root = os.path.abspath(args.dir) + print(root) + prefix_len = len(root) + 1 + for path in scan_executables(root): + name = path[prefix_len:] + try: + print('Processing:', name, file=sys.stderr) + elf = ELF.load(path) + out = os.path.join(args.output, name) + '.sym' + makedirs(os.path.dirname(out), exist_ok=True) + with open(out, 'w') as f: + elf.dump_exported_symbols(f) + except ELFError: + pass + return 0 + + +class ELFGraphCommand(Command): + def add_argparser_options(self, parser): + parser.add_argument( + '--load-extra-deps', action='append', + help='load extra module dependencies') + + parser.add_argument( + '--system', action='append', + help='path to system partition contents') + + parser.add_argument( + '--vendor', action='append', + help='path to vendor partition contents') + + parser.add_argument( + '--system-dir-as-vendor', action='append', + help='sub directory of system partition that has vendor files') + + parser.add_argument( + '--vendor-dir-as-system', action='append', + help='sub directory of vendor partition that has system files') + + +class VNDKCommand(ELFGraphCommand): + def __init__(self): + super(VNDKCommand, self).__init__( + 'vndk', help='Compute VNDK libraries set') + + def add_argparser_options(self, parser): + super(VNDKCommand, self).add_argparser_options(parser) + + parser.add_argument( + '--load-generic-refs', + help='compare with generic reference symbols') + + parser.add_argument( + '--warn-incorrect-partition', action='store_true', + help='warn about libraries only have cross partition linkages') + + parser.add_argument( + '--warn-high-level-ndk-deps', action='store_true', + help='warn about VNDK depends on high-level NDK') + + parser.add_argument( + '--warn-banned-vendor-lib-deps', action='store_true', + help='warn when a vendor binaries depends on banned lib') + + parser.add_argument( + '--ban-vendor-lib-dep', action='append', + help='library that must not be used by vendor binaries') + + def _warn_incorrect_partition_lib_set(self, lib_set, partition, error_msg): + for lib in lib_set.values(): + if not lib.users: + continue + if all((user.partition != partition for user in lib.users)): + print(error_msg.format(lib.path), file=sys.stderr) + + def _warn_incorrect_partition(self, graph): + self._warn_incorrect_partition_lib_set( + graph.lib_pt[PT_VENDOR], PT_VENDOR, + 'warning: {}: This is a vendor library with framework-only ' + 'usages.') + + self._warn_incorrect_partition_lib_set( + graph.lib_pt[PT_SYSTEM], PT_SYSTEM, + 'warning: {}: This is a framework library with vendor-only ' + 'usages.') + + def _warn_high_level_ndk_deps(self, lib_sets): + for lib_set in lib_sets: + for lib in lib_set: + for dep in lib.deps: + dep_name = os.path.basename(dep.path) + if dep_name in NDK_HIGH_LEVEL: + print('warning: {}: VNDK is using high-level NDK {}.' + .format(lib.path, dep.path), file=sys.stderr) + + def _warn_banned_vendor_lib_deps(self, graph, banned_libs): + for lib in graph.lib_pt[PT_VENDOR].values(): + for dep in lib.deps: + dep_name = os.path.basename(dep.path) + if dep_name in banned_libs: + print('warning: {}: Vendor binary depends on banned {}.' + .format(lib.path, dep.path), file=sys.stderr) + + def _check_ndk_extensions(self, graph, generic_refs): + for lib_set in (graph.lib32, graph.lib64): + for lib in lib_set.values(): + if lib.is_ndk and not generic_refs.is_equivalent_lib(lib): + print('warning: {}: NDK library should not be extended.' + .format(lib.path), file=sys.stderr) + + def main(self, args): + graph = Graph.create(args.system, args.system_dir_as_vendor, + args.vendor, args.vendor_dir_as_system, + args.load_extra_deps) + + generic_refs = None + if args.load_generic_refs: + generic_refs = GenericRefs.create_from_dir(args.load_generic_refs) + self._check_ndk_extensions(graph, generic_refs) + + if args.warn_incorrect_partition: + self._warn_incorrect_partition(graph) + + vndk_core, vndk_indirect, vndk_ext = \ + graph.compute_vndk_libs(generic_refs) + + if args.warn_high_level_ndk_deps: + self._warn_high_level_ndk_deps((vndk_core, vndk_indirect, vndk_ext)) + + if args.warn_banned_vendor_lib_deps: + if args.ban_vendor_lib_dep: + banned_libs = set(args.ban_vendor_lib_dep) + else: + banned_libs = BANNED_LIBS + self._warn_banned_vendor_lib_deps(graph, banned_libs) + + for lib in sorted_lib_path_list(vndk_core): + print('vndk-core:', lib) + for lib in sorted_lib_path_list(vndk_indirect): + print('vndk-indirect:', lib) + for lib in sorted_lib_path_list(vndk_ext): + print('vndk-ext:', lib) + + return 0 + + +class DepsCommand(ELFGraphCommand): + def __init__(self): + super(DepsCommand, self).__init__( + 'deps', help='Print binary dependencies for debugging') + + def add_argparser_options(self, parser): + super(DepsCommand, self).add_argparser_options(parser) + + parser.add_argument( + '--revert', action='store_true', + help='print usage dependency') + + parser.add_argument( + '--leaf', action='store_true', + help='print binaries without dependencies or usages') + + def main(self, args): + graph = Graph.create(args.system, args.system_dir_as_vendor, + args.vendor, args.vendor_dir_as_system, + args.load_extra_deps) + + results = [] + for partition in range(NUM_PARTITIONS): + for name, lib in graph.lib_pt[partition].items(): + assoc_libs = lib.users if args.revert else lib.deps + results.append((name, sorted_lib_path_list(assoc_libs))) + results.sort() + + if args.leaf: + for name, deps in results: + if not deps: + print(name) + else: + for name, deps in results: + print(name) + for dep in deps: + print('\t' + dep) + return 0 + +def main(): + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(dest='subcmd') + subcmds = dict() + + def register_subcmd(cmd): + subcmds[cmd.name] = cmd + cmd.add_argparser_options( + subparsers.add_parser(cmd.name, help=cmd.help)) + + register_subcmd(ELFDumpCommand()) + register_subcmd(CreateGenericRefCommand()) + register_subcmd(VNDKCommand()) + register_subcmd(DepsCommand()) + + args = parser.parse_args() + if not args.subcmd: + parser.print_help() + sys.exit(1) + return subcmds[args.subcmd].main(args) + +if __name__ == '__main__': + sys.exit(main())