// Map is so that we don't allocate multiple dex files for the same OatDexFile. static std::map<const OatDexFile*, std::unique_ptr<const DexFile>> opened_dex_files;
const DexFile* OpenDexFile(const OatDexFile* oat_dex_file, std::string* error_msg) {
DCHECK(oat_dex_file != nullptr); auto it = opened_dex_files.find(oat_dex_file); if (it != opened_dex_files.end()) { return it->second.get();
} const DexFile* ret = oat_dex_file->OpenDexFile(error_msg).release();
opened_dex_files.emplace(oat_dex_file, std::unique_ptr<const DexFile>(ret)); return ret;
}
bool Symbolize() { const InstructionSet isa = oat_file_->GetOatHeader().GetInstructionSet();
std::unique_ptr<const InstructionSetFeatures> features = InstructionSetFeatures::FromBitmap(
isa, oat_file_->GetOatHeader().GetInstructionSetFeaturesBitmap());
case OatClassType::kNoneCompiled: // Ignore. break;
}
}
}
void WalkOatClass(const OatFile::OatClass& oat_class, const DexFile& dex_file,
uint32_t class_def_index) {
ClassAccessor accessor(dex_file, class_def_index); // Note: even if this is an interface or a native class, we still have to walk it, as there // might be a static initializer.
uint32_t class_method_idx = 0; for (const ClassAccessor::Method& method : accessor.GetMethods()) {
WalkOatMethod(oat_class.GetOatMethod(class_method_idx++),
dex_file,
class_def_index,
method.GetIndex(),
method.GetCodeItem(),
method.GetAccessFlags());
}
}
// Print the key-value store.
{
os << "KEY VALUE STORE:\n";
uint32_t offset = 0; constchar* key; constchar* value; while (oat_header.GetNextStoreKeyValuePair(&offset, &key, &value)) {
os << key << " = " << value << "\n";
}
os << "\n";
}
if (options_.absolute_addresses_) {
os << "BEGIN:\n";
os << reinterpret_cast<constvoid*>(oat_file_.Begin()) << "\n\n";
os << "END:\n";
os << reinterpret_cast<constvoid*>(oat_file_.End()) << "\n\n";
}
os << "SIZE:\n";
os << oat_file_.Size() << "\n\n";
os << std::flush;
// If set, adjust relative address to be searched if (options_.addr2instr_ != 0) {
resolved_addr2instr_ = options_.addr2instr_;
os << "SEARCH ADDRESS:\n";
os << StringPrintf("0x%08x\n\n", resolved_addr2instr_);
}
// Dump .bss summary, individual entries are dumped per dex file.
os << ".bss: "; if (oat_file_.GetBssMethods().empty() && oat_file_.GetBssGcRoots().empty()) {
os << "empty.\n\n";
} else {
os << oat_file_.GetBssMethods().size() << " methods, ";
os << oat_file_.GetBssGcRoots().size() << " GC roots.\n\n";
}
// Dumping the dex file overview is compact enough to do even if header only. for (size_t i = 0; i < oat_dex_files_.size(); i++) { const OatDexFile* oat_dex_file = oat_dex_files_[i];
CHECK(oat_dex_file != nullptr);
std::string error_msg; const DexFile* const dex_file = OpenDexFile(oat_dex_file, &error_msg); if (dex_file == nullptr) {
os << "Failed to open dex file '" << oat_dex_file->GetDexFileLocation() << "': "
<< error_msg; continue;
}
if (linker != nullptr) {
ArrayRef<const DexFile* const> bcp_dex_files(linker->GetBootClassPath()); // The guarantee that we have is that we can safely take a look the BCP DexFiles in // [0..number_of_compiled_bcp_dexfiles) since the runtime may add more DexFiles after that. // As a note, in the case of not having mappings or in the case of multi image we // purposively leave `oat_file_.bcp_bss_info` empty.
CHECK_LE(oat_file_.bcp_bss_info_.size(), bcp_dex_files.size()); for (size_t i = 0; i < oat_file_.bcp_bss_info_.size(); i++) { const DexFile* const dex_file = bcp_dex_files[i];
os << "Entries for BCP DexFile: " << dex_file->GetLocation() << "\n";
DumpBssMappings(os,
dex_file,
oat_file_.bcp_bss_info_[i].method_bss_mapping,
oat_file_.bcp_bss_info_[i].type_bss_mapping,
oat_file_.bcp_bss_info_[i].public_type_bss_mapping,
oat_file_.bcp_bss_info_[i].package_type_bss_mapping,
oat_file_.bcp_bss_info_[i].string_bss_mapping,
oat_file_.bcp_bss_info_[i].method_type_bss_mapping);
}
} else { // We don't have a runtime, just dump the offsets for (size_t i = 0; i < oat_file_.bcp_bss_info_.size(); i++) {
os << "Offsets for BCP DexFile at index " << i << "\n";
DumpBssOffsets(os, "ArtMethod", oat_file_.bcp_bss_info_[i].method_bss_mapping);
DumpBssOffsets(os, "Class", oat_file_.bcp_bss_info_[i].type_bss_mapping);
DumpBssOffsets(os, "Public Class", oat_file_.bcp_bss_info_[i].public_type_bss_mapping);
DumpBssOffsets(os, "Package Class", oat_file_.bcp_bss_info_[i].package_type_bss_mapping);
DumpBssOffsets(os, "String", oat_file_.bcp_bss_info_[i].string_bss_mapping);
DumpBssOffsets(os, "MethodType", oat_file_.bcp_bss_info_[i].method_type_bss_mapping);
}
}
}
if (!options_.dump_header_only_) {
VariableIndentationOutputStream vios(&os);
VdexFile::VdexFileHeader vdex_header = oat_file_.GetVdexFile()->GetVdexFileHeader(); if (vdex_header.IsValid()) {
std::string error_msg;
std::vector<const DexFile*> dex_files; for (size_t i = 0; i < oat_dex_files_.size(); i++) { const DexFile* dex_file = OpenDexFile(oat_dex_files_[i], &error_msg); if (dex_file == nullptr) {
os << "Error opening dex file: " << error_msg << std::endl; returnfalse;
}
dex_files.push_back(dex_file);
}
verifier::VerifierDeps deps(dex_files, /*output_only=*/ false); if (!deps.ParseStoredData(dex_files, oat_file_.GetVdexFile()->GetVerifierDepsData())) {
os << "Error parsing verifier dependencies." << std::endl; returnfalse;
}
deps.Dump(&vios);
} else {
os << "UNRECOGNIZED vdex file, magic "
<< vdex_header.GetMagic()
<< ", version "
<< vdex_header.GetVdexVersion()
<< "\n";
} for (size_t i = 0; i < oat_dex_files_.size(); i++) { const OatDexFile* oat_dex_file = oat_dex_files_[i];
CHECK(oat_dex_file != nullptr); if (!DumpOatDexFile(os, *oat_dex_file)) {
success = false;
}
}
}
if (options_.export_dex_location_) {
std::string error_msg; for (const OatDexFile* oat_dex_file : oat_dex_files_) {
CHECK(oat_dex_file != nullptr); const DexFile* dex_file = OpenDexFile(oat_dex_file, &error_msg); if (dex_file == nullptr) {
os << "Failed to open dex file '" << oat_dex_file->GetDexFileLocation()
<< "': " << error_msg; returnfalse;
} if (!dex_file->IsDexContainerFirstEntry()) { // All the data was already exported together with the primary dex file. continue;
} if (!ExportDexFile(os, dex_file)) {
success = false; break;
}
}
}
private: void AddAllOffsets() { // We don't know the length of the code for each method, but we need to know where to stop // when disassembling. What we do know is that a region of code will be followed by some other // region, so if we keep a sorted sequence of the start of each region, we can infer the length // of a piece of code by using upper_bound to find the start of the next region. for (size_t i = 0; i < oat_dex_files_.size(); i++) { const OatDexFile* oat_dex_file = oat_dex_files_[i];
CHECK(oat_dex_file != nullptr);
std::string error_msg; const DexFile* const dex_file = OpenDexFile(oat_dex_file, &error_msg); if (dex_file == nullptr) {
LOG(WARNING) << "Failed to open dex file '" << oat_dex_file->GetDexFileLocation()
<< "': " << error_msg; continue;
}
offsets_.insert(reinterpret_cast<uintptr_t>(&dex_file->GetHeader())); for (ClassAccessor accessor : dex_file->GetClasses()) { const OatFile::OatClass oat_class = oat_dex_file->GetOatClass(accessor.GetClassDefIndex()); for (uint32_t class_method_index = 0;
class_method_index < accessor.NumMethods();
++class_method_index) {
AddOffsets(oat_class.GetOatMethod(class_method_index));
}
}
}
// If the last thing in the file is code for a method, there won't be an offset for the "next" // thing. Instead of having a special case in the upper_bound code, let's just add an entry // for the end of the file.
offsets_.insert(oat_file_.Size());
}
static uint32_t AlignCodeOffset(uint32_t maybe_thumb_offset) { return maybe_thumb_offset & ~0x1; // TODO: Make this Thumb2 specific.
}
bool DumpOatDexFile(std::ostream& os, const OatDexFile& oat_dex_file) { bool success = true; bool stop_analysis = false;
os << "OatDexFile:\n";
os << StringPrintf("location: %s\n", oat_dex_file.GetDexFileLocation().c_str());
os << StringPrintf("checksum: 0x%08x\n", oat_dex_file.GetDexFileLocationChecksum());
if (oat_dex_file.GetOatFile()->ContainsDexCode()) { const uint8_t* const vdex_file_begin = oat_dex_file.GetOatFile()->DexBegin();
// Print data range of the dex file embedded inside the corresponding vdex file. const uint8_t* const dex_file_pointer = oat_dex_file.GetDexFilePointer();
uint32_t dex_offset = dchecked_integral_cast<uint32_t>(dex_file_pointer - vdex_file_begin);
os << StringPrintf( "dex-file: 0x%08x..0x%08x\n",
dex_offset,
dchecked_integral_cast<uint32_t>(dex_offset + oat_dex_file.FileSize() - 1));
} else {
os << StringPrintf("dex-file not in VDEX file\n");
}
// Create the dex file early. A lot of print-out things depend on it.
std::string error_msg; const DexFile* const dex_file = OpenDexFile(&oat_dex_file, &error_msg); if (dex_file == nullptr) {
os << "NOT FOUND: " << error_msg << "\n\n";
os << std::flush; returnfalse;
}
// Print lookup table, if it exists. if (oat_dex_file.GetLookupTableData() != nullptr) {
uint32_t table_offset = dchecked_integral_cast<uint32_t>(
oat_dex_file.GetLookupTableData() - oat_dex_file.GetOatFile()->DexBegin());
uint32_t table_size = TypeLookupTable::RawDataLength(dex_file->NumClassDefs());
os << StringPrintf("type-table: 0x%08x..0x%08x\n",
table_offset,
table_offset + table_size - 1); const TypeLookupTable& lookup = oat_dex_file.GetTypeLookupTable();
lookup.Dump(os);
}
VariableIndentationOutputStream vios(&os);
ScopedIndentation indent1(&vios); for (ClassAccessor accessor : dex_file->GetClasses()) { // TODO: Support regex
std::string_view descriptor = accessor.GetDescriptorView(); if (DescriptorToDot(descriptor).find(options_.class_filter_) == std::string::npos) { continue;
}
const uint16_t class_def_index = accessor.GetClassDefIndex();
uint32_t oat_class_offset = oat_dex_file.GetOatClassOffset(class_def_index); const OatFile::OatClass oat_class = oat_dex_file.GetOatClass(class_def_index);
os << static_cast<ssize_t>(class_def_index) << ": " << descriptor << " (offset=0x"
<< StringPrintf("%08zx", AdjustOffset(oat_class_offset))
<< ") (type_idx=" << accessor.GetClassIdx().index_
<< ") (" << oat_class.GetStatus() << ")" << " (" << oat_class.GetType() << ")\n"; // TODO: include bitmap here if type is kOatClassSomeCompiled? if (options_.list_classes_) { continue;
} if (!DumpOatClass(&vios, oat_class, *dex_file, accessor, &stop_analysis)) {
success = false;
} if (stop_analysis) {
os << std::flush; return success;
}
}
os << "\n";
os << std::flush; return success;
}
// Some quick checks just in case if (fsize == 0 || fsize < sizeof(DexFile::Header)) {
os << "Invalid dex file\n"; returnfalse;
}
// We expect a reproducible checksum.
uint32_t orig_checksum = reinterpret_cast<DexFile::Header*>(const_cast<uint8_t*>(dex_file->Begin()))->checksum_; if (orig_checksum != dex_file->CalculateChecksum()) {
os << "Unexpected checksum from dex file '" << dex_file_location << "'\n"; returnfalse;
} // Extend the data range to export all the dex files in the container.
CHECK(dex_file->IsDexContainerFirstEntry()) << dex_file_location;
// Verify output directory exists if (!OS::DirectoryExists(options_.export_dex_location_)) { // TODO: Extend OS::DirectoryExists if symlink support is required
os << options_.export_dex_location_ << " output directory not found or symlink\n"; returnfalse;
}
// A more elegant approach to efficiently name user installed apps is welcome if (dex_orig_name.size() == 8 &&
dex_orig_name.compare("base.apk") == 0 &&
dex_orig_pos != std::string::npos) {
dex_file_location.erase(dex_orig_pos, strlen("base.apk") + 1);
size_t apk_orig_pos = dex_file_location.rfind('/'); if (apk_orig_pos != std::string::npos) {
dex_orig_name = dex_file_location.substr(++apk_orig_pos);
}
}
std::string out_dex_path(options_.export_dex_location_); if (out_dex_path.back() != '/') {
out_dex_path.append("/");
}
out_dex_path.append(dex_orig_name);
out_dex_path.append("_export.dex"); if (out_dex_path.length() > PATH_MAX) { returnfalse;
}
std::unique_ptr<File> file(OS::CreateEmptyFile(out_dex_path.c_str())); if (file.get() == nullptr) {
os << "Failed to open output dex file " << out_dex_path; returnfalse;
}
bool success = file->WriteFully(dex_file->Begin(), fsize); if (!success) {
os << "Failed to write dex file";
file->Erase(); returnfalse;
}
if (file->FlushCloseOrErase() != 0) {
os << "Flush and close failed"; returnfalse;
}
os << StringPrintf("Dex file exported at %s (%zd bytes)\n", out_dex_path.c_str(), fsize);
os << std::flush;
std::unique_ptr<StackHandleScope<1>> hs;
std::unique_ptr<verifier::MethodVerifier> verifier; if (Runtime::Current() != nullptr) { // We need to have the handle scope stay live until after the verifier since the verifier has // a handle to the dex cache from hs.
ScopedObjectAccess soa(Thread::Current());
hs.reset(new StackHandleScope<1>(Thread::Current()));
vios->Stream() << "VERIFIER TYPE ANALYSIS:\n";
ScopedIndentation indent2(vios);
DumpVerifier(vios,
soa,
hs.get(),
dex_method_idx,
&dex_file,
class_def,
code_item,
method_access_flags);
}
{
vios->Stream() << "OatMethodOffsets "; if (options_.absolute_addresses_) {
vios->Stream() << StringPrintf("%p ", oat_method_offsets);
}
vios->Stream() << StringPrintf("(offset=0x%08zx)\n", AdjustOffset(oat_method_offsets_offset)); if (oat_method_offsets_offset > oat_file_.Size()) {
vios->Stream() << StringPrintf( "WARNING: oat method offsets offset 0x%08zx is past end of file 0x%08zx.\n",
AdjustOffset(oat_method_offsets_offset),
AdjustOffset(oat_file_.Size())); // If we can't read OatMethodOffsets, the rest of the data is dangerous to read.
vios->Stream() << std::flush; returnfalse;
}
ScopedIndentation indent2(vios);
vios->Stream() << StringPrintf("code_offset: 0x%08zx ", AdjustOffset(code_offset));
uint32_t aligned_code_begin = AlignCodeOffset(oat_method.GetCodeOffset()); if (aligned_code_begin > oat_file_.Size()) {
vios->Stream() << StringPrintf( "WARNING: code offset 0x%08zx is past end of file 0x%08zx.\n",
AdjustOffset(aligned_code_begin),
AdjustOffset(oat_file_.Size()));
success = false;
}
vios->Stream() << "\n";
}
{
vios->Stream() << "OatQuickMethodHeader ";
uint32_t method_header_offset = oat_method.GetOatQuickMethodHeaderOffset(); const OatQuickMethodHeader* method_header = oat_method.GetOatQuickMethodHeader(); if (method_header != nullptr && AddStatsObject(method_header)) {
stats_["QuickMethodHeader"].AddBytes(sizeof(*method_header));
} if (options_.absolute_addresses_) {
vios->Stream() << StringPrintf("%p ", method_header);
}
vios->Stream() << StringPrintf("(offset=0x%08zx)\n", AdjustOffset(method_header_offset)); if (method_header_offset > oat_file_.Size() || sizeof(OatQuickMethodHeader) > oat_file_.Size() - method_header_offset) {
vios->Stream() << StringPrintf( "WARNING: oat quick method header at offset 0x%08zx is past end of file 0x%08zx.\n",
AdjustOffset(method_header_offset),
AdjustOffset(oat_file_.Size())); // If we can't read the OatQuickMethodHeader, the rest of the data is dangerous to read.
vios->Stream() << std::flush; returnfalse;
}
ScopedIndentation indent2(vios); if (aligned_code_begin > oat_file_.Size()) {
vios->Stream() << StringPrintf( "WARNING: start of code at 0x%08zx is past end of file 0x%08zx.",
AdjustOffset(aligned_code_begin),
AdjustOffset(oat_file_.Size()));
success = false;
} elseif (aligned_code_end > oat_file_.Size()) {
vios->Stream() << StringPrintf( "WARNING: end of code at 0x%08zx is past end of file 0x%08zx. code size is 0x%08x.\n",
AdjustOffset(aligned_code_end),
AdjustOffset(oat_file_.Size()),
code_size);
success = false; if (options_.disassemble_code_) { if (aligned_code_begin + kPrologueBytes <= oat_file_.Size()) {
DumpCode(vios, oat_method, code_item_accessor, true, kPrologueBytes);
}
}
} elseif (code_size > kMaxCodeSize) {
vios->Stream() << StringPrintf( "WARNING: " "code size %d is bigger than max expected threshold of %d. " "code size is 0x%08x.\n",
code_size,
kMaxCodeSize,
code_size);
success = false; if (options_.disassemble_code_) { if (aligned_code_begin + kPrologueBytes <= oat_file_.Size()) {
DumpCode(vios, oat_method, code_item_accessor, true, kPrologueBytes);
}
}
} elseif (options_.disassemble_code_) {
DumpCode(vios, oat_method, code_item_accessor, !success, 0);
}
}
}
vios->Stream() << std::flush; return success;
}
void DumpSpillMask(std::ostream& os, uint32_t spill_mask, bool is_float) { if (spill_mask == 0) { return;
}
os << "("; for (size_t i = 0; i < 32; i++) { if ((spill_mask & (1 << i)) != 0) { if (is_float) {
os << "fr" << i;
} else {
os << "r" << i;
}
spill_mask ^= 1 << i; // clear bit if (spill_mask != 0) {
os << ", ";
} else { break;
}
}
}
os << ")";
}
// Display data stored at the the vmap offset of an oat method. void DumpVmapData(VariableIndentationOutputStream* vios, const OatFile::OatMethod& oat_method, const CodeItemDataAccessor& code_item_accessor) { if (IsMethodGeneratedByOptimizingCompiler(oat_method, code_item_accessor)) { // The optimizing compiler outputs its CodeInfo data in the vmap table. const uint8_t* raw_code_info = oat_method.GetVmapTable(); if (raw_code_info != nullptr) {
CodeInfo code_info(raw_code_info);
DCHECK(code_item_accessor.HasCodeItem());
ScopedIndentation indent1(vios);
DumpCodeInfo(vios, code_info, oat_method);
}
} else { // Otherwise, there is nothing to display.
}
}
// Display a CodeInfo object emitted by the optimizing compiler. void DumpCodeInfo(VariableIndentationOutputStream* vios, const CodeInfo& code_info, const OatFile::OatMethod& oat_method) {
code_info.Dump(vios,
AdjustOffset(oat_method.GetCodeOffset()),
options_.dump_code_info_stack_maps_,
instruction_set_);
}
staticint GetOutVROffset(uint16_t out_num, InstructionSet isa) { // According to stack model, the first out is above the Method referernce. returnstatic_cast<size_t>(InstructionSetPointerSize(isa)) + out_num * sizeof(uint32_t);
}
static uint32_t GetVRegOffsetFromQuickCode(const CodeItemDataAccessor& code_item_accessor,
uint32_t core_spills,
uint32_t fp_spills,
size_t frame_size, int reg,
InstructionSet isa) {
PointerSize pointer_size = InstructionSetPointerSize(isa); if (kIsDebugBuild) { auto* runtime = Runtime::Current(); if (runtime != nullptr) {
CHECK_EQ(runtime->GetClassLinker()->GetImagePointerSize(), pointer_size);
}
}
DCHECK_ALIGNED(frame_size, kStackAlignment);
DCHECK_NE(reg, -1); int spill_size = POPCOUNT(core_spills) * GetBytesPerGprSpillLocation(isa)
+ POPCOUNT(fp_spills) * GetBytesPerFprSpillLocation(isa)
+ sizeof(uint32_t); // Filler. int num_regs = code_item_accessor.RegistersSize() - code_item_accessor.InsSize(); int temp_threshold = code_item_accessor.RegistersSize(); constint max_num_special_temps = 1; if (reg == temp_threshold) { // The current method pointer corresponds to special location on stack. return0;
} elseif (reg >= temp_threshold + max_num_special_temps) { /* *Specialtemporariesmayhavecustomlocationsandthelogicabovedealswiththat. *However,non-specialtemporariesareplacedrelativetotheouts.
*/ int temps_start = code_item_accessor.OutsSize() * sizeof(uint32_t)
+ static_cast<size_t>(pointer_size) /* art method */; int relative_offset = (reg - (temp_threshold + max_num_special_temps)) * sizeof(uint32_t); return temps_start + relative_offset;
} elseif (reg < num_regs) { int locals_start = frame_size - spill_size - num_regs * sizeof(uint32_t); return locals_start + (reg * sizeof(uint32_t));
} else { // Handle ins. return frame_size + ((reg - num_regs) * sizeof(uint32_t))
+ static_cast<size_t>(pointer_size) /* art method */;
}
}
// Has `oat_method` -- corresponding to the Dex `code_item` -- been compiled by // the optimizing compiler? staticbool IsMethodGeneratedByOptimizingCompiler( const OatFile::OatMethod& oat_method, const CodeItemDataAccessor& code_item_accessor) { // If the native GC map is null and the Dex `code_item` is not // null, then this method has been compiled with the optimizing // compiler. return oat_method.GetQuickCode() != nullptr &&
oat_method.GetVmapTable() != nullptr &&
code_item_accessor.HasCodeItem();
}
void DumpVerifier(VariableIndentationOutputStream* vios,
ScopedObjectAccess& soa,
StackHandleScope<1>* hs,
uint32_t dex_method_idx, const DexFile* dex_file, const dex::ClassDef& class_def, const dex::CodeItem* code_item,
uint32_t method_access_flags)
REQUIRES_SHARED(Locks::mutator_lock_) { if ((method_access_flags & kAccNative) == 0) {
Runtime* const runtime = Runtime::Current();
DCHECK(options_.class_loader_ != nullptr);
Handle<mirror::DexCache> dex_cache = hs->NewHandle(
runtime->GetClassLinker()->RegisterDexFile(*dex_file, options_.class_loader_->Get()));
CHECK(dex_cache != nullptr);
ArtMethod* method = runtime->GetClassLinker()->ResolveMethodId(
dex_method_idx, dex_cache, *options_.class_loader_); if (method == nullptr) {
soa.Self()->ClearException(); return;
}
verifier::MethodVerifier::VerifyMethodAndDump(
soa.Self(),
vios,
dex_method_idx, // Note this may be different than `dex_file` in case the file was // already registered.
dex_cache->GetDexFile(),
dex_cache,
*options_.class_loader_,
class_def,
code_item,
method_access_flags, /* api_level= */ 0);
}
}
void DumpBssOffsets(std::ostream& os, constchar* slot_type, const IndexBssMapping* mapping) {
os << ".bss offset for " << slot_type << ": "; if (mapping == nullptr) {
os << "empty.\n"; return;
}
os << "Mapping size: " << mapping->size() << "\n"; for (size_t i = 0; i < mapping->size(); ++i) {
os << "Entry[" << i << "]: index_and_mask: "
<< mapping->At(i).index_and_mask
<< ", bss_offset: "
<< mapping->At(i).bss_offset << "\n";
}
// TODO(solanes, 154012332): We are dumping the raw values but we could make assumptions about // ordering of the entries and deconstruct even the `index_and_mask`. This would allow us to use // DumpBssEntries and dump more information. The size and alignment of the entry (ArtMethod* // depends on instruction set but Class and String references are 32-bit) and the difference // from the previous `bss_offset` (or from the "oatbss" symbol for the first item) tell us how // many .bss entries a single `IndexBssMappingEntry` should describe. So we know how many most // significant set bits represent the mask and the rest is the actual index. And the position of // the mask bits would allow reconstructing the other indexes.
}
// Adjusts an offset relative to the OAT file begin to an offset relative to the ELF file begin.
size_t AdjustOffset(size_t offset) const { return (offset > 0) ? (oat_offset_ + offset) : 0; }
for (size_t i = 0; i < ImageHeader::ImageSections::kSectionCount; i++) {
ImageHeader::ImageSections index = ImageHeader::ImageSections(i); constchar* name = ImageHeader::GetImageSectionName(index);
stats_.art_file_stats[name].AddBytes(image_header_.GetImageSection(index).Size());
}
stats_.object_stats.AddBytes(image_header_.GetObjectsSection().Size());
stats_.Dump(os);
os << "\n";
staticvoid PrintField(std::ostream& os, ArtField* field, ObjPtr<mirror::Object> obj)
REQUIRES_SHARED(Locks::mutator_lock_) {
os << StringPrintf("%s: ", field->GetName()); switch (field->GetTypeAsPrimitiveType()) { case Primitive::kPrimLong:
os << StringPrintf("%" PRId64 " (0x%" PRIx64 ")\n", field->Get64(obj), field->Get64(obj)); break; case Primitive::kPrimDouble:
os << StringPrintf("%f (%a)\n", field->GetDouble(obj), field->GetDouble(obj)); break; case Primitive::kPrimFloat:
os << StringPrintf("%f (%a)\n", field->GetFloat(obj), field->GetFloat(obj)); break; case Primitive::kPrimInt:
os << StringPrintf("%d (0x%x)\n", field->Get32(obj), field->Get32(obj)); break; case Primitive::kPrimChar:
os << StringPrintf("%u (0x%x)\n", field->GetChar(obj), field->GetChar(obj)); break; case Primitive::kPrimShort:
os << StringPrintf("%d (0x%x)\n", field->GetShort(obj), field->GetShort(obj)); break; case Primitive::kPrimBoolean:
os << StringPrintf("%s (0x%x)\n", field->GetBoolean(obj) ? "true" : "false",
field->GetBoolean(obj)); break; case Primitive::kPrimByte:
os << StringPrintf("%d (0x%x)\n", field->GetByte(obj), field->GetByte(obj)); break; case Primitive::kPrimNot: { // Get the value, don't compute the type unless it is non-null as we don't want // to cause class loading.
ObjPtr<mirror::Object> value = field->GetObj(obj); if (value == nullptr) {
os << StringPrintf("null %s\n", PrettyDescriptor(field->GetTypeDescriptor()).c_str());
} else { // Grab the field type without causing resolution.
ObjPtr<mirror::Class> field_type = field->LookupResolvedType(); if (field_type != nullptr) {
PrettyObjectValue(os, field_type, value);
} else {
os << StringPrintf("%p %s\n",
value.Ptr(),
PrettyDescriptor(field->GetTypeDescriptor()).c_str());
}
} break;
} default:
os << "unexpected field type: " << field->GetTypeDescriptor() << "\n"; break;
}
}
staticvoid DumpFields(std::ostream& os, mirror::Object* obj, ObjPtr<mirror::Class> klass)
REQUIRES_SHARED(Locks::mutator_lock_) {
ObjPtr<mirror::Class> super = klass->GetSuperClass(); if (super != nullptr) {
DumpFields(os, obj, super);
} for (ArtField& field : klass->GetFields()) { if (!field.IsStatic()) {
PrintField(os, &field, obj);
}
}
}
std::set<constvoid*> already_seen_; // Compute the size of the given data within the oat file and whether this is the first time // this data has been requested
size_t ComputeOatSize(constvoid* oat_data, bool* first_occurrence) { if (already_seen_.count(oat_data) == 0) {
*first_occurrence = true;
already_seen_.insert(oat_data);
} else {
*first_occurrence = false;
} return oat_dumper_->ComputeSize(oat_data);
}
// Dump methods whose size is a certain number of standard deviations from the mean
size_t dumped_values = 0;
size_t skipped_values = 0; for (size_t i = 100; i > 0; i--) { // i is the current number of standard deviations
size_t cur_size_variance = i * i * size_variance; bool first = true; for (size_t j = 0; j < n; j++) {
size_t cur_size = method_outlier_size[j]; if (cur_size > size_mean) {
size_t cur_var = cur_size - size_mean;
cur_var = cur_var * cur_var; if (cur_var > cur_size_variance) { if (dumped_values > 20) { if (i == 1) {
skipped_values++;
} else {
i = 2; // jump to counting for 1 standard deviation break;
}
} else { if (first) {
os << "\nBig methods (size > " << i << " standard deviations the norm):\n";
first = false;
}
os << ArtMethod::PrettyMethod(method_outlier[j]) << " requires storage of "
<< PrettySize(cur_size) << "\n";
method_outlier_size[j] = 0; // don't consider this method again
dumped_values++;
}
}
}
}
} if (skipped_values > 0) {
os << "... skipped " << skipped_values
<< " methods with size > 1 standard deviation from the norm\n";
}
os << std::flush;
// Dump methods whose expansion is a certain number of standard deviations from the mean
dumped_values = 0;
skipped_values = 0; for (size_t i = 10; i > 0; i--) { // i is the current number of standard deviations double cur_expansion_variance = i * i * expansion_variance; bool first = true; for (size_t j = 0; j < n; j++) { double cur_expansion = method_outlier_expansion[j]; if (cur_expansion > expansion_mean) {
size_t cur_var = cur_expansion - expansion_mean;
cur_var = cur_var * cur_var; if (cur_var > cur_expansion_variance) { if (dumped_values > 20) { if (i == 1) {
skipped_values++;
} else {
i = 2; // jump to counting for 1 standard deviation break;
}
} else { if (first) {
os << "\nLarge expansion methods (size > " << i
<< " standard deviations the norm):\n";
first = false;
}
os << ArtMethod::PrettyMethod(method_outlier[j]) << " expanded code by "
<< cur_expansion << "\n";
method_outlier_expansion[j] = 0.0; // don't consider this method again
dumped_values++;
}
}
}
}
} if (skipped_values > 0) {
os << "... skipped " << skipped_values
<< " methods with expansion > 1 standard deviation from the norm\n";
}
os << "\n" << std::flush;
}
void Dump(std::ostream& os)
REQUIRES_SHARED(Locks::mutator_lock_) {
VariableIndentationOutputStream vios(&os);
art_file_stats.DumpSizes(vios, "ArtFile");
os << "\n" << std::flush;
object_stats.DumpSizes(vios, "Objects");
os << "\n" << std::flush;
oat_file_stats.DumpSizes(vios, "OatFile");
os << "\n" << std::flush;
for (const std::pair<std::string, size_t>& oat_dex_file_size : oat_dex_file_sizes) {
os << StringPrintf("%s = %zd (%2.0f%% of oat file bytes)\n",
oat_dex_file_size.first.c_str(), oat_dex_file_size.second,
PercentOfOatBytes(oat_dex_file_size.second));
}
os << "\n" << StringPrintf("vmap_table_bytes = %7zd (%2.0f%% of oat file bytes)\n\n",
vmap_table_bytes, PercentOfOatBytes(vmap_table_bytes))
<< std::flush;
private: enum { // Number of bytes for a constructor to be considered large. Based on the 1000 basic block // threshold, we assume 2 bytes per instruction and 2 instructions per block.
kLargeConstructorDexBytes = 4000, // Number of bytes for a method to be considered large. Based on the 4000 basic block // threshold, we assume 2 bytes per instruction and 2 instructions per block.
kLargeMethodDexBytes = 16000
};
// For performance, use the *os_ directly for anything that doesn't need indentation // and prepare an indentation stream with default indentation 1.
std::ostream* os_;
VariableIndentationOutputStream vios_;
ScopedIndentation indent1_;
staticint DumpImages(Runtime* runtime, OatDumperOptions* options, std::ostream* os) { // Dumping the image, no explicit class loader.
ScopedNullHandle<mirror::ClassLoader> null_class_loader;
options->class_loader_ = &null_class_loader;
if (options->app_image_ != nullptr) { if (!options->oat_filename_.has_value()) {
LOG(ERROR) << "Can not dump app image without app oat file"; return EXIT_FAILURE;
} // We can't know if the app image is 32 bits yet, but it contains pointers into the oat file. // We need to map the oat file in the low 4gb or else the fixup wont be able to fit oat file // pointers into 32 bit pointer sized ArtMethods.
std::string error_msg;
std::unique_ptr<OatFile> oat_file =
OpenOat(*options->oat_filename_, options->dex_filename_, &error_msg); if (oat_file == nullptr) {
LOG(ERROR) << "Failed to open oat file " << *options->oat_filename_ << " with error "
<< error_msg; return EXIT_FAILURE;
}
std::unique_ptr<gc::space::ImageSpace> space(
gc::space::ImageSpace::CreateFromAppImage(options->app_image_, oat_file.get(), &error_msg)); if (space == nullptr) {
LOG(ERROR) << "Failed to open app image " << options->app_image_ << " with error "
<< error_msg; return EXIT_FAILURE;
} // Open dex files for the image.
ScopedObjectAccess soa(Thread::Current());
std::vector<std::unique_ptr<const DexFile>> dex_files; if (!OpenImageDexFiles(space.get(), &dex_files, &error_msg)) {
LOG(ERROR) << "Failed to open app image dex files " << options->app_image_ << " with error "
<< error_msg; return EXIT_FAILURE;
} // Dump the actual image. return DumpImage(space.get(), options, os);
}
gc::Heap* heap = runtime->GetHeap(); if (!heap->HasBootImageSpace()) {
LOG(ERROR) << "No image spaces"; return EXIT_FAILURE;
}
ScopedObjectAccess soa(Thread::Current()); for (gc::space::ImageSpace* image_space : heap->GetBootImageSpaces()) { int result = DumpImage(image_space, options, os); if (result != EXIT_SUCCESS) { return result;
}
} return EXIT_SUCCESS;
}
// Need a class loader. Fake that we're a compiler. // Note: this will run initializers through the unstarted runtime, so make sure it's // initialized.
interpreter::UnstartedRuntime::Initialize();
// Need to register dex files to get a working dex cache. for (const DexFile* dex_file : *class_path) {
ObjPtr<mirror::DexCache> dex_cache = class_linker->RegisterDexFile(
*dex_file, self->DecodeJObject(class_loader)->AsClassLoader());
CHECK(dex_cache != nullptr);
}
// Use the class loader while dumping.
StackHandleScope<1> scope(soa.Self());
Handle<mirror::ClassLoader> loader_handle = scope.NewHandle(
soa.Decode<mirror::ClassLoader>(class_loader));
options->class_loader_ = &loader_handle;
bool result; // Try to produce an ELF file of the same type. This is finicky, as we have used 32-bit ELF // files for 64-bit code in the past. if (Is64BitInstructionSet(oat_file->GetOatHeader().GetInstructionSet())) {
OatSymbolizer<ElfTypes64> oat_symbolizer(oat_file.get(), output_name, no_bits);
result = oat_symbolizer.Symbolize();
} else {
OatSymbolizer<ElfTypes32> oat_symbolizer(oat_file.get(), output_name, no_bits);
result = oat_symbolizer.Symbolize();
} if (!result) {
LOG(ERROR) << "Failed to symbolize"; return EXIT_FAILURE;
}
for (const std::string& line : lines) { // A line should be either a class descriptor, in which case we will dump the complete IMT, // or a class descriptor and an interface method, in which case we will lookup the method, // determine its IMT slot, and check the class' IMT.
size_t first_space = line.find(' '); if (first_space == std::string::npos) {
DumpIMTForClass(runtime, line, h_class_loader, &prepared);
} else {
DumpIMTForMethod(runtime,
line.substr(0, first_space),
line.substr(first_space + 1, std::string::npos),
h_class_loader,
&prepared);
}
std::cerr << std::endl;
}
// Return whether the given class has no IMT (or the one shared with java.lang.Object). staticbool HasNoIMT(Runtime* runtime,
Handle<mirror::Class> klass, const PointerSize pointer_size,
std::unordered_set<std::string>* prepared)
REQUIRES_SHARED(Locks::mutator_lock_) { if (klass->IsObjectClass() || !klass->ShouldHaveImt()) { returntrue;
}
if (klass->GetImt(pointer_size) == nullptr) {
PrepareClass(runtime, klass, prepared);
}
std::cerr << " Interfaces:" << std::endl; // Run through iftable, find methods that slot here, see if they fit.
ObjPtr<mirror::IfTable> if_table = klass->GetIfTable(); for (size_t i = 0, num_interfaces = klass->GetIfTableCount(); i < num_interfaces; ++i) {
ObjPtr<mirror::Class> iface = if_table->GetInterface(i);
std::string iface_name;
std::cerr << " " << iface->GetDescriptor(&iface_name) << std::endl;
for (ArtMethod& iface_method : iface->GetMethods(pointer_size)) { if (!iface_method.IsVirtual()) { continue;
}
uint32_t class_hash, name_hash, signature_hash;
ImTable::GetImtHashComponents(*iface_method.GetDexFile(),
iface_method.GetDexMethodIndex(),
&class_hash,
&name_hash,
&signature_hash);
uint32_t imt_slot = ImTable::GetImtIndex(&iface_method); // Note: For default methods we use the dex method index for calculating the slot. // For abstract methods the compile-time constant `kImTableHashUseName` determines // whether we use the component hashes (current behavior) or the dex method index.
std::cerr << " " << iface_method.PrettyMethod(true)
<< " slot=" << imt_slot
<< " dex_method_index=" << iface_method.GetDexMethodIndex()
<< std::hex
<< " class_hash=0x" << class_hash
<< " name_hash=0x" << name_hash
<< " signature_hash=0x" << signature_hash
<< std::dec
<< std::endl;
}
}
}
size_t table_index = 0; for (;;) {
ArtMethod* ptr2 = current_table->GetInterfaceMethod(table_index, pointer_size); if (ptr2 == nullptr) { break;
}
table_index++;
std::string p_name = ptr2->PrettyMethod(true); if (p_name.starts_with(method)) {
std::cerr << " Slot "
<< index
<< " ("
<< current_table->NumEntries(pointer_size)
<< ")"
<< std::endl;
PrintTable(current_table, pointer_size); return;
}
}
} else {
std::string p_name = ptr->PrettyMethod(true); if (p_name.starts_with(method)) {
std::cerr << " Slot " << index << " (1)" << std::endl;
std::cerr << " " << p_name << std::endl;
} else { // Run through iftable, find methods that slot here, see if they fit.
ObjPtr<mirror::IfTable> if_table = klass->GetIfTable(); for (size_t i = 0, num_interfaces = klass->GetIfTableCount(); i < num_interfaces; ++i) {
ObjPtr<mirror::Class> iface = if_table->GetInterface(i);
size_t num_methods = iface->NumDeclaredVirtualMethods(); if (num_methods > 0) { for (ArtMethod& iface_method : iface->GetMethods(pointer_size)) { if (ImTable::GetImtIndex(&iface_method) == index) {
std::string i_name = iface_method.PrettyMethod(true); if (i_name.starts_with(method)) {
std::cerr << " Slot " << index << " (1)" << std::endl;
std::cerr << " " << p_name << " (" << i_name << ")" << std::endl;
}
}
}
}
}
}
}
}
}
// Read lines from the given stream, dropping comments and empty lines static std::vector<std::string> ReadCommentedInputStream(std::istream& in_stream) {
std::vector<std::string> output; while (in_stream.good()) {
std::string dot;
std::getline(in_stream, dot); if (dot.starts_with("#") || dot.empty()) { continue;
}
output.push_back(dot);
} return output;
}
// Read lines from the given file, dropping comments and empty lines. static std::vector<std::string> ReadCommentedInputFromFile(const std::string& input_filename) {
std::unique_ptr<std::ifstream> input_file(new std::ifstream(input_filename, std::ifstream::in)); if (input_file.get() == nullptr) {
LOG(ERROR) << "Failed to open input file " << input_filename; return std::vector<std::string>();
}
std::vector<std::string> result = ReadCommentedInputStream(*input_file);
input_file->close(); return result;
}
// Prepare a class, i.e., ensure it has a filled IMT. Will do so recursively for superclasses, // and note in the given set that the work was done. staticvoid PrepareClass(Runtime* runtime,
Handle<mirror::Class> h_klass, /*inout*/ std::unordered_set<std::string>* done)
REQUIRES_SHARED(Locks::mutator_lock_) { if (!h_klass->ShouldHaveImt()) { return;
}
std::string name;
name = h_klass->GetDescriptor(&name);
if (done->find(name) != done->end()) { return;
}
done->insert(name);
if (h_klass->HasSuperClass()) {
StackHandleScope<1> h(Thread::Current());
PrepareClass(runtime, h.NewHandle<mirror::Class>(h_klass->GetSuperClass()), done);
}
if (!h_klass->IsTemp()) {
runtime->GetClassLinker()->FillIMTAndConflictTables(h_klass.Get());
}
}
};
ParseStatus ParseChecks(std::string* error_msg) override { if (image_location_ != nullptr) { if (!boot_image_locations_.empty()) {
std::cerr << "Warning: Invalid combination of --boot-image and --image\n";
std::cerr << "Use --image alone to dump boot image(s)\n";
std::cerr << "Ignoring --boot-image\n";
std::cerr << "\n";
boot_image_locations_.clear();
}
Split(image_location_, ':', &boot_image_locations_);
}
// Perform the parent checks.
ParseStatus parent_checks = Base::ParseChecks(error_msg); if (parent_checks != kParseOk) { return parent_checks;
}
// Perform our own checks. if (image_location_ == nullptr && app_image_ == nullptr && oat_filename_ == nullptr) {
*error_msg = "Either --image, --app-image, --oat-file, or --symbolize must be specified"; return kParseError;
}
if (app_image_ != nullptr && image_location_ != nullptr) {
std::cerr << "Warning: Combining --app-image with --image is no longer supported\n";
std::cerr << "Use --app-image alone to dump an app image, and optionally pass --boot-image " "to specify the boot image that the app image is based on\n";
std::cerr << "Use --image alone to dump boot image(s)\n";
std::cerr << "Ignoring --image\n";
std::cerr << "\n";
image_location_ = nullptr;
}
if (image_location_ != nullptr && oat_filename_ != nullptr) {
*error_msg = "--image and --oat-file must not be specified together\n" "Use --image alone to dump both boot image(s) and their oat file(s)\n" "Use --oat-file alone to dump an oat file"; return kParseError;
}
if (app_oat_ != nullptr) {
std::cerr << "Warning: --app-oat is deprecated. Use --oat-file instead\n";
std::cerr << "\n";
oat_filename_ = app_oat_;
}
if (boot_image_locations_.empty() && app_image_ != nullptr) { // At this point, boot image inference is impossible or has failed, and the user has been // warned about the failure. // When dumping an app image, we need at least one valid boot image, so we have to stop. // When dumping other things, we can continue to start the runtime in imageless mode.
*error_msg = "--boot-image must be specified"; return kParseError;
}
Examples:
- Dump a primary boot image with its oat file.
oatdump --image=/system/framework/boot.art
- Dump a primary boot image and extension(s) with their oat files.
oatdump --image=/system/framework/boot.art:/system/framework/boot-framework-adservices.art
- Dump an app image with its oat file.
oatdump --app-image=app.art --oat-file=app.odex [--dex-file=app.apk] [--boot-image=boot.art]
- Dump an app oat file.
oatdump --oat-file=app.odex [--dex-file=app.apk] [--boot-image=boot.art]
- Dump IMT collisions. (See --dump-imt for details.)
oatdump --oat-file=app.odex --dump-imt=imt.txt [--dex-file=app.apk] [--boot-image=boot.art]
[--dump-imt-stats]
- Symbolize an oat file. (See --symbolize for details.)
oatdump --symbolize=app.odex [--dex-file=app.apk] [--only-keep-debug]
Options:
--oat-file=<file.oat>: dumps an oat file with the given filename.
Example: --oat-file=/system/framework/arm64/boot.oat
--image=<file.art>: dumps boot image(s) specified at the given location.
Example: --image=/system/framework/boot.art
--app-image=<file.art>: dumps an app image with the given filename.
Must also have a specified app oat file (with --oat-file).
Example: --app-image=app.art
--app-oat=<file.odex>: deprecated. Use --oat-file instead.
)";
usage += Base::GetUsage();
usage += // Optional. " --no-dump:vmap may be used to disable vmap dumping.\n" " Example: --no-dump:vmap\n" "\n" " --dump:code_info_stack_maps enables dumping of stack maps in CodeInfo sections.\n" " Example: --dump:code_info_stack_maps\n" "\n" " --no-disassemble may be used to disable disassembly.\n" " Example: --no-disassemble\n" "\n" " --header-only may be used to print only the oat header.\n" " Example: --header-only\n" "\n" " --list-classes may be used to list target file classes (can be used with filters).\n" " Example: --list-classes\n" " Example: --list-classes --class-filter=com.example.foo\n" "\n" " --list-methods may be used to list target file methods (can be used with filters).\n" " Example: --list-methods\n" " Example: --list-methods --class-filter=com.example --method-filter=foo\n" "\n" " --symbolize=<file.oat>: output a copy of file.oat with elf symbols included.\n" " Example: --symbolize=/system/framework/boot.oat\n" "\n" " --only-keep-debug: modifies the behaviour of --symbolize so that\n" " .rodata and .text sections are omitted in the output file to save space.\n" " Example: --symbolize=/system/framework/boot.oat --only-keep-debug\n" "\n" " --class-filter=<class name>: only dumps classes that contain the filter.\n" " Example: --class-filter=com.example.foo\n" "\n" " --method-filter=<method name>: only dumps methods that contain the filter.\n" " Example: --method-filter=foo\n" "\n" " --dump-method-and-offset-as-json: dumps fully qualified method names and\n" " signatures ONLY, in a standard json format.\n" " Example: --dump-method-and-offset-as-json\n" "\n" " --export-dex-to=<directory>: may be used to export oat embedded dex files.\n" " Example: --export-dex-to=/data/local/tmp\n" "\n" " --addr2instr=<address>: output matching method disassembled code from relative\n" " address (e.g. PC from crash dump)\n" " Example: --addr2instr=0x00001a3b\n" "\n" " --dump-imt=<file.txt>: output IMT collisions (if any) for the given receiver\n" " types and interface methods in the given file. The file\n" " is read line-wise, where each line should either be a class\n" " name or descriptor, or a class name/descriptor and a prefix\n" " of a complete method name (separated by a whitespace).\n" " Example: --dump-imt=imt.txt\n" "\n" " --dump-imt-stats: modifies the behavior of --dump-imt to also output IMT statistics\n" " for the boot image.\n" " Example: --dump-imt-stats" "\n";
return usage;
}
public:
OatDumpMode GetMode() { // Keep the order of precedence for backward compatibility. if (symbolize_) { return OatDumpMode::kSymbolize;
} if (!imt_dump_.empty()) { return OatDumpMode::kDumpImt;
} if (image_location_ != nullptr || app_image_ != nullptr) { return OatDumpMode::kDumpImage;
}
CHECK_NE(oat_filename_, nullptr); return OatDumpMode::kDumpOat;
}
if (mode == OatDumpMode::kSymbolize) { // ELF has special kind of section called SHT_NOBITS which allows us to create // sections which exist but their data is omitted from the ELF file to save space. // This is what "strip --only-keep-debug" does when it creates separate ELF file // with only debug data. We use it in similar way to exclude .rodata and .text. bool no_bits = args_->only_keep_debug_; return SymbolizeOat(
args_->oat_filename_, args_->dex_filename_, args_->output_name_, no_bits) ==
EXIT_SUCCESS;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.