I want to make a little modification to the Android source to meet my requirement. Here is the requirement:
I want index all the objects in an Android app by adding one more public int
field to the java.lang.Object
class. Therefore, all the classes can inherit the newly added field because all of them are the subclasses of the Object
class.
What I have done so far is modify the java.lang.Object
class under <Android_source>/libcore/libart/src/main/java/java/lang
folder and recompile the source.
I want to ask if I am doing the right thing. Can my Android app recognize this change (e.g. can a String
object access the newly added field)?
Edit
After around 3 weeks try and error, I finally got the complete answer. I want to share this experience with others if anybody else want to modify the core java libraries of Android source (e.g., modifying Object.java
and String.java
etc.). Again, as mentioned by Michael, please note that such a modification may only be suitable for research or testing purpose.
The key challenge in making a successful modification (here 'successful' means the modified Android source can be built and run on emulators or real devices without any problem) is that some of the classes in the core java library have their C++ mirrors (located in <Android_source>/art/runtime/mirrors/
). When modifying these java classes, you should also make the same modifications to their C++ mirrors. Otherwise, you could fail the build process because there are a bunch of checkings that you need to pass. Since I only add a new field to the Object.java
, I will list some checkings (or requirements) I encountered below:
1.The size of an object instance = the size of its C++ mirror. For example, if I add a long
field into Object.java
, I should also add a uint64_t
field to its C++ mirror to make their size equal.
2.Try to make the size of an object instance be the power of 2 (e.g., 2, 4, 8, 16, ...). For example, the size of the original Object.java
is 8, therefore I add a long field to increase the size to 16. If I add an int field, the size becomes 12 and it can fail many checkings. I don't figure out the exact reason but I guess it has something to do with memory alignment.
3.Try to put primitive-type fields after non-primitive-type fields and primitive-type fields should be ordered by size. This means you should put reference-type fields in the front, followed by 8-byte-primitive-type fields, then 4-byte-primitive-type fields, then 2-byte-primitive-type fields, then 1-byte-primitive-type fields. Again, I guess the reason is memory alignment
That's all I done to meet my requirements. I am open to any discussions if you have any ideas about the purpose of these checkings (especially the 2ed and 3rd one)
New edit
More specifically, I did the following things:
Add a new field (e.g., public long tag;
) in Object.java
Change static constexpr uint32_t kObjectHeaderSize = kUseBrooksReadBarrier ? 16 : 8;
to static constexpr uint32_t kObjectHeaderSize = kUseBrooksReadBarrier ? 24 : 16;
in Object.h
Add the following method in Object.h
(Only on Android 7)
static MemberOffset TagOffset() {
return OFFSET_OF_OBJECT_MEMBER(Object, tag);
}
Add a new public field public: uint64_t tag;
in Object.h
Change
#define MIRROR_OBJECT_CLASS_OFFSET 0
ADD_TEST_EQ(MIRROR_OBJECT_CLASS_OFFSET, art::mirror::Object::ClassOffset().Int32Value())
#define MIRROR_OBJECT_LOCK_WORD_OFFSET 4
ADD_TEST_EQ(MIRROR_OBJECT_LOCK_WORD_OFFSET, art::mirror::Object::MonitorOffset().Int32Value())
#if defined(USE_BROOKS_READ_BARRIER)
#define MIRROR_OBJECT_HEADER_SIZE 16
#else
#define MIRROR_OBJECT_HEADER_SIZE 8
to
#define MIRROR_OBJECT_CLASS_OFFSET 0
ADD_TEST_EQ(MIRROR_OBJECT_CLASS_OFFSET, art::mirror::Object::ClassOffset().Int32Value())
#define MIRROR_OBJECT_LOCK_WORD_OFFSET 4
ADD_TEST_EQ(MIRROR_OBJECT_LOCK_WORD_OFFSET, art::mirror::Object::MonitorOffset().Int32Value())
#define MIRROR_OBJECT_CLASS_TAG 8
ADD_TEST_EQ(MIRROR_OBJECT_CLASS_TAG, art::mirror::Object::TagOffset().Int32Value())
#if defined(USE_BROOKS_READ_BARRIER)
#define MIRROR_OBJECT_HEADER_SIZE 24
#else
#define MIRROR_OBJECT_HEADER_SIZE 16
in asm_support.h
(Only on Android 7)
Add addOffset(OFFSETOF_MEMBER(mirror::Object, tag), "tag");
in class_linker_test.cc
Change
static_assert(kObjectHeaderSize == sizeof(mirror::HeapReference<mirror::Class>) +
sizeof(LockWord),
to
static_assert(kObjectHeaderSize == sizeof(mirror::HeapReference<mirror::Class>) +
sizeof(LockWord) + 8,
in art/runtime/gc/collector/concurrent_copying.cc
8 Change static constexpr size_t kFirstElementOffset = 12u;
to static constexpr size_t kFirstElementOffset = 20u;
in array.h
9 Change static constexpr size_t kObjectAlignmentShift = 3;
to static constexpr size_t kObjectAlignmentShift = 4;
in runtime_globals.h
(Not done yet)
10 Change
static_assert(kObjectAlignment == 8, "Alignment check");
class PACKED(8) ImageHeader {
to
static_assert(kObjectAlignment == 16, "Alignment check");
class PACKED(16) ImageHeader {
in image.h
(Not done yet)
11 Change static constexpr size_t kAlignment = 8;
to static constexpr size_t kAlignment = 16;
in gc::space::BumpPointerSpace
(Not done yet)
12 Change #!/usr/bin/python
to #!/usr/local/bin/python
in device/generic/goldfish/tools/mk_combined_img.py
(The value depends on your /bin/env python)(Only on Android 10)
13 Change
#define DCHECK_ALIGNED_PARAM(value, alignment) \
DCHECK(::art::IsAlignedParam(value, alignment)) << reinterpret_cast<const void*>(value)
to
#define DCHECK_ALIGNED_PARAM(value, alignment) \
DCHECK(::art::IsAlignedParam(value, alignment)) << reinterpret_cast<const void*>(value) << "," << alignment
in art/libartbase/base/bit_utils.h
(for debug purpose)(Only for Android 11)
14 Change
DCHECK_ALIGNED_PARAM(remaining_space, object_class->GetObjectSize());
Object* end = dst + remaining_space / object_class->GetObjectSize();
to
DCHECK_ALIGNED_PARAM(remaining_space, kObjectAlignment);
Object* end = dst + remaining_space / kObjectAlignment;
in art/dex2oat/linker/image_writer.cc
(Only for Android 11)
memcpy(reinterpret_cast<uint8_t*>(to_ref) + kObjectHeaderSize,
reinterpret_cast<const uint8_t*>(from_ref) + kObjectHeaderSize,
obj_size - kObjectHeaderSize);
to
memcpy(reinterpret_cast<uint8_t*>(to_ref) + kObjectHeaderSize - 8,
reinterpret_cast<const uint8_t*>(from_ref) + kObjectHeaderSize - 8,
obj_size - kObjectHeaderSize + 8);
in concurrent_copying.cc
(on Android 10)(reference)
Firstly, I'd like to state that I don't think this is a good idea and is probably overkill outside of research purposes. If you are modifying AOSP then the code you write will be dependent on the target device running that customised build of AOSP. However, it is still possible.
I'm assuming you already know how to compile and flash a custom AOSP build to a device. In order to write code that makes use of your new functionality, you'll also need to compile a custom SDK. This is so that Android Studio will know that your new method exists within Object
, and can compile correctly against it. The full documentation can be found here, but essentially it boils down to:
. build/envsetup.sh
lunch sdk-eng
make sdk
When you have your SDK zip file, you'll need to unzip it to your SDK's platforms
directory - it should now show up checked in your SDK manager. If you have given your SDK a custom platform ID then you should be able to use that in your build.gradle
files.
Disclaimer: This advice is purely from memory, it's a lengthy process so I've not had time to double-check, and chances are there may be a couple of minor things I've missed. This should get you most of the way towards where you want to be though.