upload android base code part3

This commit is contained in:
August 2018-08-08 16:48:17 +08:00
parent 71b83c22f1
commit b9e30e05b1
15122 changed files with 2089659 additions and 0 deletions

View file

@ -0,0 +1,38 @@
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// TODO(ajcbik): rename dexdump2 into dexdump when Dalvik version is removed
art_cc_binary {
name: "dexdump2",
host_supported: true,
srcs: [
"dexdump_cfg.cc",
"dexdump_main.cc",
"dexdump.cc",
],
cflags: ["-Wall"],
shared_libs: [
"libart",
"libbase",
],
}
art_cc_test {
name: "art_dexdump_tests",
defaults: [
"art_gtest_defaults",
],
srcs: ["dexdump_test.cc"],
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,61 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Header file of the dexdump utility.
*
* This is a re-implementation of the original dexdump utility that was
* based on Dalvik functions in libdex into a new dexdump that is now
* based on Art functions in libart instead. The output is identical to
* the original for correct DEX files. Error messages may differ, however.
* Also, ODEX files are no longer supported.
*/
#ifndef ART_DEXDUMP_DEXDUMP_H_
#define ART_DEXDUMP_DEXDUMP_H_
#include <stdint.h>
#include <stdio.h>
namespace art {
/* Supported output formats. */
enum OutputFormat {
OUTPUT_PLAIN = 0, // default
OUTPUT_XML, // XML-style
};
/* Command-line options. */
struct Options {
bool checksumOnly;
bool disassemble;
bool exportsOnly;
bool ignoreBadChecksum;
bool showAnnotations;
bool showCfg;
bool showFileHeaders;
bool showSectionHeaders;
bool verbose;
OutputFormat outputFormat;
const char* outputFileName;
};
/* Prototypes. */
extern struct Options gOptions;
extern FILE* gOutFile;
int processFile(const char* fileName);
} // namespace art
#endif // ART_DEXDUMP_DEXDUMP_H_

View file

@ -0,0 +1,392 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Implementation file for control flow graph dumping for the dexdump utility.
*/
#include "dexdump_cfg.h"
#include <inttypes.h>
#include <ostream>
#include <map>
#include <set>
#include "dex_file-inl.h"
#include "dex_instruction-inl.h"
namespace art {
static void dumpMethodCFGImpl(const DexFile* dex_file,
uint32_t dex_method_idx,
const DexFile::CodeItem* code_item,
std::ostream& os) {
os << "digraph {\n";
os << " # /* " << dex_file->PrettyMethod(dex_method_idx, true) << " */\n";
std::set<uint32_t> dex_pc_is_branch_target;
{
// Go and populate.
const Instruction* inst = Instruction::At(code_item->insns_);
for (uint32_t dex_pc = 0;
dex_pc < code_item->insns_size_in_code_units_;
dex_pc += inst->SizeInCodeUnits(), inst = inst->Next()) {
if (inst->IsBranch()) {
dex_pc_is_branch_target.insert(dex_pc + inst->GetTargetOffset());
} else if (inst->IsSwitch()) {
const uint16_t* insns = code_item->insns_ + dex_pc;
int32_t switch_offset = insns[1] | (static_cast<int32_t>(insns[2]) << 16);
const uint16_t* switch_insns = insns + switch_offset;
uint32_t switch_count = switch_insns[1];
int32_t targets_offset;
if ((*insns & 0xff) == Instruction::PACKED_SWITCH) {
/* 0=sig, 1=count, 2/3=firstKey */
targets_offset = 4;
} else {
/* 0=sig, 1=count, 2..count*2 = keys */
targets_offset = 2 + 2 * switch_count;
}
for (uint32_t targ = 0; targ < switch_count; targ++) {
int32_t offset =
static_cast<int32_t>(switch_insns[targets_offset + targ * 2]) |
static_cast<int32_t>(switch_insns[targets_offset + targ * 2 + 1] << 16);
dex_pc_is_branch_target.insert(dex_pc + offset);
}
}
}
}
// Create nodes for "basic blocks."
std::map<uint32_t, uint32_t> dex_pc_to_node_id; // This only has entries for block starts.
std::map<uint32_t, uint32_t> dex_pc_to_incl_id; // This has entries for all dex pcs.
{
const Instruction* inst = Instruction::At(code_item->insns_);
bool first_in_block = true;
bool force_new_block = false;
for (uint32_t dex_pc = 0;
dex_pc < code_item->insns_size_in_code_units_;
dex_pc += inst->SizeInCodeUnits(), inst = inst->Next()) {
if (dex_pc == 0 ||
(dex_pc_is_branch_target.find(dex_pc) != dex_pc_is_branch_target.end()) ||
force_new_block) {
uint32_t id = dex_pc_to_node_id.size();
if (id > 0) {
// End last node.
os << "}\"];\n";
}
// Start next node.
os << " node" << id << " [shape=record,label=\"{";
dex_pc_to_node_id.insert(std::make_pair(dex_pc, id));
first_in_block = true;
force_new_block = false;
}
// Register instruction.
dex_pc_to_incl_id.insert(std::make_pair(dex_pc, dex_pc_to_node_id.size() - 1));
// Print instruction.
if (!first_in_block) {
os << " | ";
} else {
first_in_block = false;
}
// Dump the instruction. Need to escape '"', '<', '>', '{' and '}'.
os << "<" << "p" << dex_pc << ">";
os << " 0x" << std::hex << dex_pc << std::dec << ": ";
std::string inst_str = inst->DumpString(dex_file);
size_t cur_start = 0; // It's OK to start at zero, instruction dumps don't start with chars
// we need to escape.
while (cur_start != std::string::npos) {
size_t next_escape = inst_str.find_first_of("\"{}<>", cur_start + 1);
if (next_escape == std::string::npos) {
os << inst_str.substr(cur_start, inst_str.size() - cur_start);
break;
} else {
os << inst_str.substr(cur_start, next_escape - cur_start);
// Escape all necessary characters.
while (next_escape < inst_str.size()) {
char c = inst_str.at(next_escape);
if (c == '"' || c == '{' || c == '}' || c == '<' || c == '>') {
os << '\\' << c;
} else {
break;
}
next_escape++;
}
if (next_escape >= inst_str.size()) {
next_escape = std::string::npos;
}
cur_start = next_escape;
}
}
// Force a new block for some fall-throughs and some instructions that terminate the "local"
// control flow.
force_new_block = inst->IsSwitch() || inst->IsBasicBlockEnd();
}
// Close last node.
if (dex_pc_to_node_id.size() > 0) {
os << "}\"];\n";
}
}
// Create edges between them.
{
std::ostringstream regular_edges;
std::ostringstream taken_edges;
std::ostringstream exception_edges;
// Common set of exception edges.
std::set<uint32_t> exception_targets;
// These blocks (given by the first dex pc) need exception per dex-pc handling in a second
// pass. In the first pass we try and see whether we can use a common set of edges.
std::set<uint32_t> blocks_with_detailed_exceptions;
{
uint32_t last_node_id = std::numeric_limits<uint32_t>::max();
uint32_t old_dex_pc = 0;
uint32_t block_start_dex_pc = std::numeric_limits<uint32_t>::max();
const Instruction* inst = Instruction::At(code_item->insns_);
for (uint32_t dex_pc = 0;
dex_pc < code_item->insns_size_in_code_units_;
old_dex_pc = dex_pc, dex_pc += inst->SizeInCodeUnits(), inst = inst->Next()) {
{
auto it = dex_pc_to_node_id.find(dex_pc);
if (it != dex_pc_to_node_id.end()) {
if (!exception_targets.empty()) {
// It seems the last block had common exception handlers. Add the exception edges now.
uint32_t node_id = dex_pc_to_node_id.find(block_start_dex_pc)->second;
for (uint32_t handler_pc : exception_targets) {
auto node_id_it = dex_pc_to_incl_id.find(handler_pc);
if (node_id_it != dex_pc_to_incl_id.end()) {
exception_edges << " node" << node_id
<< " -> node" << node_id_it->second << ":p" << handler_pc
<< ";\n";
}
}
exception_targets.clear();
}
block_start_dex_pc = dex_pc;
// Seems to be a fall-through, connect to last_node_id. May be spurious edges for things
// like switch data.
uint32_t old_last = last_node_id;
last_node_id = it->second;
if (old_last != std::numeric_limits<uint32_t>::max()) {
regular_edges << " node" << old_last << ":p" << old_dex_pc
<< " -> node" << last_node_id << ":p" << dex_pc
<< ";\n";
}
}
// Look at the exceptions of the first entry.
CatchHandlerIterator catch_it(*code_item, dex_pc);
for (; catch_it.HasNext(); catch_it.Next()) {
exception_targets.insert(catch_it.GetHandlerAddress());
}
}
// Handle instruction.
// Branch: something with at most two targets.
if (inst->IsBranch()) {
const int32_t offset = inst->GetTargetOffset();
const bool conditional = !inst->IsUnconditional();
auto target_it = dex_pc_to_node_id.find(dex_pc + offset);
if (target_it != dex_pc_to_node_id.end()) {
taken_edges << " node" << last_node_id << ":p" << dex_pc
<< " -> node" << target_it->second << ":p" << (dex_pc + offset)
<< ";\n";
}
if (!conditional) {
// No fall-through.
last_node_id = std::numeric_limits<uint32_t>::max();
}
} else if (inst->IsSwitch()) {
// TODO: Iterate through all switch targets.
const uint16_t* insns = code_item->insns_ + dex_pc;
/* make sure the start of the switch is in range */
int32_t switch_offset = insns[1] | (static_cast<int32_t>(insns[2]) << 16);
/* offset to switch table is a relative branch-style offset */
const uint16_t* switch_insns = insns + switch_offset;
uint32_t switch_count = switch_insns[1];
int32_t targets_offset;
if ((*insns & 0xff) == Instruction::PACKED_SWITCH) {
/* 0=sig, 1=count, 2/3=firstKey */
targets_offset = 4;
} else {
/* 0=sig, 1=count, 2..count*2 = keys */
targets_offset = 2 + 2 * switch_count;
}
/* make sure the end of the switch is in range */
/* verify each switch target */
for (uint32_t targ = 0; targ < switch_count; targ++) {
int32_t offset =
static_cast<int32_t>(switch_insns[targets_offset + targ * 2]) |
static_cast<int32_t>(switch_insns[targets_offset + targ * 2 + 1] << 16);
int32_t abs_offset = dex_pc + offset;
auto target_it = dex_pc_to_node_id.find(abs_offset);
if (target_it != dex_pc_to_node_id.end()) {
// TODO: value label.
taken_edges << " node" << last_node_id << ":p" << dex_pc
<< " -> node" << target_it->second << ":p" << (abs_offset)
<< ";\n";
}
}
}
// Exception edges. If this is not the first instruction in the block
if (block_start_dex_pc != dex_pc) {
std::set<uint32_t> current_handler_pcs;
CatchHandlerIterator catch_it(*code_item, dex_pc);
for (; catch_it.HasNext(); catch_it.Next()) {
current_handler_pcs.insert(catch_it.GetHandlerAddress());
}
if (current_handler_pcs != exception_targets) {
exception_targets.clear(); // Clear so we don't do something at the end.
blocks_with_detailed_exceptions.insert(block_start_dex_pc);
}
}
if (inst->IsReturn() ||
(inst->Opcode() == Instruction::THROW) ||
(inst->IsBranch() && inst->IsUnconditional())) {
// No fall-through.
last_node_id = std::numeric_limits<uint32_t>::max();
}
}
// Finish up the last block, if it had common exceptions.
if (!exception_targets.empty()) {
// It seems the last block had common exception handlers. Add the exception edges now.
uint32_t node_id = dex_pc_to_node_id.find(block_start_dex_pc)->second;
for (uint32_t handler_pc : exception_targets) {
auto node_id_it = dex_pc_to_incl_id.find(handler_pc);
if (node_id_it != dex_pc_to_incl_id.end()) {
exception_edges << " node" << node_id
<< " -> node" << node_id_it->second << ":p" << handler_pc
<< ";\n";
}
}
exception_targets.clear();
}
}
// Second pass for detailed exception blocks.
// TODO
// Exception edges. If this is not the first instruction in the block
for (uint32_t dex_pc : blocks_with_detailed_exceptions) {
const Instruction* inst = Instruction::At(&code_item->insns_[dex_pc]);
uint32_t this_node_id = dex_pc_to_incl_id.find(dex_pc)->second;
while (true) {
CatchHandlerIterator catch_it(*code_item, dex_pc);
if (catch_it.HasNext()) {
std::set<uint32_t> handled_targets;
for (; catch_it.HasNext(); catch_it.Next()) {
uint32_t handler_pc = catch_it.GetHandlerAddress();
auto it = handled_targets.find(handler_pc);
if (it == handled_targets.end()) {
auto node_id_it = dex_pc_to_incl_id.find(handler_pc);
if (node_id_it != dex_pc_to_incl_id.end()) {
exception_edges << " node" << this_node_id << ":p" << dex_pc
<< " -> node" << node_id_it->second << ":p" << handler_pc
<< ";\n";
}
// Mark as done.
handled_targets.insert(handler_pc);
}
}
}
if (inst->IsBasicBlockEnd()) {
break;
}
// Loop update. Have a break-out if the next instruction is a branch target and thus in
// another block.
dex_pc += inst->SizeInCodeUnits();
if (dex_pc >= code_item->insns_size_in_code_units_) {
break;
}
if (dex_pc_to_node_id.find(dex_pc) != dex_pc_to_node_id.end()) {
break;
}
inst = inst->Next();
}
}
// Write out the sub-graphs to make edges styled.
os << "\n";
os << " subgraph regular_edges {\n";
os << " edge [color=\"#000000\",weight=.3,len=3];\n\n";
os << " " << regular_edges.str() << "\n";
os << " }\n\n";
os << " subgraph taken_edges {\n";
os << " edge [color=\"#00FF00\",weight=.3,len=3];\n\n";
os << " " << taken_edges.str() << "\n";
os << " }\n\n";
os << " subgraph exception_edges {\n";
os << " edge [color=\"#FF0000\",weight=.3,len=3];\n\n";
os << " " << exception_edges.str() << "\n";
os << " }\n\n";
}
os << "}\n";
}
void DumpMethodCFG(const DexFile* dex_file, uint32_t dex_method_idx, std::ostream& os) {
// This is painful, we need to find the code item. That means finding the class, and then
// iterating the table.
if (dex_method_idx >= dex_file->NumMethodIds()) {
os << "Could not find method-idx.";
return;
}
const DexFile::MethodId& method_id = dex_file->GetMethodId(dex_method_idx);
const DexFile::ClassDef* class_def = dex_file->FindClassDef(method_id.class_idx_);
if (class_def == nullptr) {
os << "Could not find class-def.";
return;
}
const uint8_t* class_data = dex_file->GetClassData(*class_def);
if (class_data == nullptr) {
os << "No class data.";
return;
}
ClassDataItemIterator it(*dex_file, class_data);
it.SkipAllFields();
// Find method, and dump it.
while (it.HasNextDirectMethod() || it.HasNextVirtualMethod()) {
uint32_t method_idx = it.GetMemberIndex();
if (method_idx == dex_method_idx) {
dumpMethodCFGImpl(dex_file, dex_method_idx, it.GetMethodCodeItem(), os);
return;
}
it.Next();
}
// Otherwise complain.
os << "Something went wrong, didn't find the method in the class data.";
}
} // namespace art

View file

@ -0,0 +1,31 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ART_DEXDUMP_DEXDUMP_CFG_H_
#define ART_DEXDUMP_DEXDUMP_CFG_H_
#include <inttypes.h>
#include <ostream>
namespace art {
class DexFile;
void DumpMethodCFG(const DexFile* dex_file, uint32_t dex_method_idx, std::ostream& os);
} // namespace art
#endif // ART_DEXDUMP_DEXDUMP_CFG_H_

View file

@ -0,0 +1,156 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Main driver of the dexdump utility.
*
* This is a re-implementation of the original dexdump utility that was
* based on Dalvik functions in libdex into a new dexdump that is now
* based on Art functions in libart instead. The output is very similar to
* to the original for correct DEX files. Error messages may differ, however.
* Also, ODEX files are no longer supported.
*/
#include "dexdump.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "base/logging.h"
#include "runtime.h"
#include "mem_map.h"
namespace art {
static const char* gProgName = "dexdump";
/*
* Shows usage.
*/
static void usage(void) {
fprintf(stderr, "Copyright (C) 2007 The Android Open Source Project\n\n");
fprintf(stderr, "%s: [-a] [-c] [-d] [-e] [-f] [-h] [-i] [-l layout] [-o outfile]"
" dexfile...\n\n", gProgName);
fprintf(stderr, " -a : display annotations\n");
fprintf(stderr, " -c : verify checksum and exit\n");
fprintf(stderr, " -d : disassemble code sections\n");
fprintf(stderr, " -e : display exported items only\n");
fprintf(stderr, " -f : display summary information from file header\n");
fprintf(stderr, " -g : display CFG for dex\n");
fprintf(stderr, " -h : display file header details\n");
fprintf(stderr, " -i : ignore checksum failures\n");
fprintf(stderr, " -l : output layout, either 'plain' or 'xml'\n");
fprintf(stderr, " -o : output file name (defaults to stdout)\n");
}
/*
* Main driver of the dexdump utility.
*/
int dexdumpDriver(int argc, char** argv) {
// Art specific set up.
InitLogging(argv, Runtime::Abort);
MemMap::Init();
// Reset options.
bool wantUsage = false;
memset(&gOptions, 0, sizeof(gOptions));
gOptions.verbose = true;
// Parse all arguments.
while (1) {
const int ic = getopt(argc, argv, "acdefghil:o:");
if (ic < 0) {
break; // done
}
switch (ic) {
case 'a': // display annotations
gOptions.showAnnotations = true;
break;
case 'c': // verify the checksum then exit
gOptions.checksumOnly = true;
break;
case 'd': // disassemble Dalvik instructions
gOptions.disassemble = true;
break;
case 'e': // exported items only
gOptions.exportsOnly = true;
break;
case 'f': // display outer file header
gOptions.showFileHeaders = true;
break;
case 'g': // display cfg
gOptions.showCfg = true;
break;
case 'h': // display section headers, i.e. all meta-data
gOptions.showSectionHeaders = true;
break;
case 'i': // continue even if checksum is bad
gOptions.ignoreBadChecksum = true;
break;
case 'l': // layout
if (strcmp(optarg, "plain") == 0) {
gOptions.outputFormat = OUTPUT_PLAIN;
} else if (strcmp(optarg, "xml") == 0) {
gOptions.outputFormat = OUTPUT_XML;
gOptions.verbose = false;
} else {
wantUsage = true;
}
break;
case 'o': // output file
gOptions.outputFileName = optarg;
break;
default:
wantUsage = true;
break;
} // switch
} // while
// Detect early problems.
if (optind == argc) {
fprintf(stderr, "%s: no file specified\n", gProgName);
wantUsage = true;
}
if (gOptions.checksumOnly && gOptions.ignoreBadChecksum) {
fprintf(stderr, "Can't specify both -c and -i\n");
wantUsage = true;
}
if (wantUsage) {
usage();
return 2;
}
// Open alternative output file.
if (gOptions.outputFileName) {
gOutFile = fopen(gOptions.outputFileName, "w");
if (!gOutFile) {
fprintf(stderr, "Can't open %s\n", gOptions.outputFileName);
return 1;
}
}
// Process all files supplied on command line.
int result = 0;
while (optind < argc) {
result |= processFile(argv[optind++]);
} // while
return result != 0;
}
} // namespace art
int main(int argc, char** argv) {
return art::dexdumpDriver(argc, argv);
}

View file

@ -0,0 +1,82 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <string>
#include <vector>
#include <sstream>
#include <sys/types.h>
#include <unistd.h>
#include "common_runtime_test.h"
#include "runtime/arch/instruction_set.h"
#include "runtime/exec_utils.h"
#include "runtime/os.h"
#include "runtime/utils.h"
#include "utils.h"
namespace art {
class DexDumpTest : public CommonRuntimeTest {
protected:
virtual void SetUp() {
CommonRuntimeTest::SetUp();
// Dogfood our own lib core dex file.
dex_file_ = GetLibCoreDexFileNames()[0];
}
// Runs test with given arguments.
bool Exec(const std::vector<std::string>& args, std::string* error_msg) {
// TODO(ajcbik): dexdump2 -> dexdump
std::string file_path = GetTestAndroidRoot() + "/bin/dexdump2";
EXPECT_TRUE(OS::FileExists(file_path.c_str())) << file_path << " should be a valid file path";
std::vector<std::string> exec_argv = { file_path };
exec_argv.insert(exec_argv.end(), args.begin(), args.end());
return ::art::Exec(exec_argv, error_msg);
}
std::string dex_file_;
};
TEST_F(DexDumpTest, NoInputFileGiven) {
std::string error_msg;
ASSERT_FALSE(Exec({}, &error_msg)) << error_msg;
}
TEST_F(DexDumpTest, CantOpenOutput) {
std::string error_msg;
ASSERT_FALSE(Exec({"-o", "/joho", dex_file_}, &error_msg)) << error_msg;
}
TEST_F(DexDumpTest, BadFlagCombination) {
std::string error_msg;
ASSERT_FALSE(Exec({"-c", "-i", dex_file_}, &error_msg)) << error_msg;
}
TEST_F(DexDumpTest, FullPlainOutput) {
std::string error_msg;
ASSERT_TRUE(Exec({"-d", "-f", "-h", "-l", "plain", "-o", "/dev/null",
dex_file_}, &error_msg)) << error_msg;
}
TEST_F(DexDumpTest, XMLOutput) {
std::string error_msg;
ASSERT_TRUE(Exec({"-l", "xml", "-o", "/dev/null",
dex_file_}, &error_msg)) << error_msg;
}
} // namespace art