1 /*
2 * Copyright (c) 2021-2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "aot_manager.h"
17 #include "aotdump_options.h"
18 #include "class_data_accessor.h"
19 #include "file.h"
20 #include "file-inl.h"
21 #include "mem/arena_allocator.h"
22 #include "mem/gc/gc_types.h"
23 #include "mem/pool_manager.h"
24 #include "method_data_accessor.h"
25 #include "method_data_accessor-inl.h"
26 #include "proto_data_accessor.h"
27 #include "proto_data_accessor-inl.h"
28 #include "runtime/include/class_helper.h"
29 #include "utils/arch.h"
30 #include "utils/bit_memory_region-inl.h"
31
32 #ifdef PANDA_COMPILER_TARGET_AARCH64
33 #include "aarch64/disasm-aarch64.h"
34 using vixl::aarch64::Decoder;
35 using vixl::aarch64::Disassembler;
36 using vixl::aarch64::Instruction;
37 #endif // PANDA_COMPILER_TARGET_AARCH64
38 #ifdef PANDA_COMPILER_TARGET_X86_64
39 #include "Zydis/Zydis.h"
40 #endif // PANDA_COMPILER_TARGET_X86_64
41
42 #include <fstream>
43 #include <iomanip>
44 #include <elf.h>
45 #include <regex>
46
47 using namespace panda::compiler; // NOLINT(google-build-using-namespace)
48
49 namespace panda::aoutdump {
50
51 class TypePrinter {
52 public:
TypePrinter(panda_file::ProtoDataAccessor & pda,std::ostream & out)53 explicit TypePrinter(panda_file::ProtoDataAccessor &pda, std::ostream &out) : pda_(pda), out_(out) {}
54 NO_COPY_SEMANTIC(TypePrinter);
55 NO_MOVE_SEMANTIC(TypePrinter);
56 ~TypePrinter() = default;
57
Dump(panda_file::Type type)58 void Dump(panda_file::Type type)
59 {
60 if (!type.IsReference()) {
61 out_ << type;
62 } else {
63 out_ << ClassHelper::GetName(pda_.GetPandaFile().GetStringData(pda_.GetReferenceType(refIdx_++)).data);
64 }
65 }
66
67 private:
68 panda_file::ProtoDataAccessor &pda_;
69 std::ostream &out_;
70 uint32_t refIdx_ {0};
71 };
72
73 class PandaFileHelper {
74 public:
PandaFileHelper(const char * fileName)75 explicit PandaFileHelper(const char *fileName) : file_(panda_file::OpenPandaFile(fileName)) {}
76 NO_COPY_SEMANTIC(PandaFileHelper);
77 NO_MOVE_SEMANTIC(PandaFileHelper);
78 ~PandaFileHelper() = default;
79
GetMethodName(uint32_t id) const80 std::string GetMethodName(uint32_t id) const
81 {
82 if (!file_) {
83 return "-";
84 }
85 auto fileId = panda_file::File::EntityId(id);
86 panda_file::MethodDataAccessor mda(*file_, fileId);
87 panda_file::ProtoDataAccessor pda(*file_, panda_file::MethodDataAccessor::GetProtoId(*file_, fileId));
88 std::ostringstream ss;
89 TypePrinter typePrinter(pda, ss);
90
91 typePrinter.Dump(pda.GetReturnType());
92 ss << ' ';
93
94 auto className = ClassHelper::GetName(file_->GetStringData(mda.GetClassId()).data);
95 ss << className << "::" << file_->GetStringData(mda.GetNameId()).data;
96
97 ss << '(';
98 bool firstArg = true;
99 // inject class name as the first argument of non static method for consitency with panda::Method::GetFullName
100 if (!mda.IsStatic()) {
101 firstArg = false;
102 ss << className;
103 }
104 for (uint32_t argIdx = 0; argIdx < pda.GetNumArgs(); ++argIdx) {
105 if (!firstArg) {
106 ss << ", ";
107 }
108 firstArg = false;
109 typePrinter.Dump(pda.GetArgType(argIdx));
110 }
111 ss << ')';
112 return ss.str();
113 }
114
GetClassName(uint32_t id) const115 std::string GetClassName(uint32_t id) const
116 {
117 if (!file_) {
118 return "-";
119 }
120 return ClassHelper::GetName(file_->GetStringData(panda_file::File::EntityId(id)).data);
121 }
122
123 private:
124 std::unique_ptr<const panda_file::File> file_;
125 };
126
127 class AotDump {
128 public:
AotDump(panda::ArenaAllocator * allocator)129 explicit AotDump(panda::ArenaAllocator *allocator) : allocator_(allocator) {}
130 NO_COPY_SEMANTIC(AotDump);
131 NO_MOVE_SEMANTIC(AotDump);
132 ~AotDump() = default;
133
Run(int argc,const char * argv[])134 int Run(int argc, const char *argv[]) // NOLINT(modernize-avoid-c-arrays)
135 {
136 panda::PandArgParser paParser;
137 PandArg<std::string> inputFile {"input_file", "", "Input file path"};
138 paParser.EnableTail();
139 paParser.PushBackTail(&inputFile);
140 std::array<char, NAME_MAX> tmpfileBuf {"/tmp/fixed_aot_XXXXXX"};
141 // Remove temporary file at the function exit
142 auto finalizer = [](const char *fileName) {
143 if (fileName != nullptr) {
144 remove(fileName);
145 }
146 };
147 std::unique_ptr<const char, decltype(finalizer)> tempFileRemover(nullptr, finalizer);
148
149 panda::Span<const char *> sp(argv, argc);
150 panda::aoutdump::Options options(sp[0]);
151 options.AddOptions(&paParser);
152 if (!paParser.Parse(argc, argv)) {
153 std::cerr << "Parse options failed: " << paParser.GetErrorString() << std::endl;
154 return -1;
155 }
156 if (inputFile.GetValue().empty()) {
157 std::cerr << "Please specify input file\n";
158 return -1;
159 }
160 Expected<std::unique_ptr<AotFile>, std::string> aotRes;
161 // Fix elf header for cross platform files. Cross opening is available only in X86_64 arch.
162 if (RUNTIME_ARCH == Arch::X86_64) {
163 if (!FixElfHeader(tmpfileBuf, inputFile)) {
164 return -1;
165 }
166 tempFileRemover.reset(tmpfileBuf.data());
167 aotRes = AotFile::Open(tmpfileBuf.data(), 0, true);
168 } else {
169 aotRes = AotFile::Open(inputFile.GetValue(), 0, true);
170 }
171 if (!aotRes) {
172 std::cerr << "Open AOT file failed: " << aotRes.Error() << std::endl;
173 return -1;
174 }
175
176 DumpAll(std::move(aotRes.Value()), options);
177 return 0;
178 }
179
DumpAll(std::unique_ptr<AotFile> aotFile,panda::aoutdump::Options options)180 void DumpAll(std::unique_ptr<AotFile> aotFile, panda::aoutdump::Options options)
181 {
182 std::ostream *outputStream;
183 std::ofstream outFstream;
184 if (options.WasSetOutputFile()) {
185 outFstream.open(options.GetOutputFile());
186 outputStream = &outFstream;
187 } else {
188 outputStream = &std::cerr;
189 }
190 auto &stream = *outputStream;
191 DumpHeader(stream, aotFile);
192 DumpFiles(stream, aotFile, options);
193 }
194
FixElfHeader(std::array<char,NAME_MAX> & tmpfileBuf,PandArg<std::string> & inputFile)195 bool FixElfHeader(std::array<char, NAME_MAX> &tmpfileBuf, PandArg<std::string> &inputFile)
196 {
197 int fd = mkstemp(tmpfileBuf.data());
198 if (fd == -1) {
199 std::cerr << "Failed to open temporary file\n";
200 return false;
201 }
202 close(fd);
203 std::ofstream ostm(tmpfileBuf.data(), std::ios::binary);
204 std::ifstream istm(inputFile.GetValue(), std::ios::binary);
205 if (!ostm.is_open() || !istm.is_open()) {
206 std::cerr << "Cannot open tmpfile or input file\n";
207 return false;
208 }
209 std::vector<char> buffer(std::istreambuf_iterator<char>(istm), {});
210 auto *header = reinterpret_cast<Elf64_Ehdr *>(buffer.data());
211 header->e_machine = EM_X86_64;
212 ostm.write(buffer.data(), buffer.size());
213 return true;
214 }
215
DumpHeader(std::ostream & stream,std::unique_ptr<panda::compiler::AotFile> & aotFile)216 void DumpHeader(std::ostream &stream, std::unique_ptr<panda::compiler::AotFile> &aotFile)
217 {
218 auto aotHeader = aotFile->GetAotHeader();
219 stream << "header:" << std::endl;
220 stream << " magic: " << aotHeader->magic.data() << std::endl;
221 stream << " version: " << aotHeader->version.data() << std::endl;
222 stream << " filename: " << aotFile->GetFileName() << std::endl;
223 stream << " cmdline: " << aotFile->GetCommandLine() << std::endl;
224 stream << " checksum: " << aotHeader->checksum << std::endl;
225 stream << " env checksum: " << aotHeader->environmentChecksum << std::endl;
226 stream << " arch: " << GetArchString(static_cast<Arch>(aotHeader->arch)) << std::endl;
227 stream << " gc_type: " << mem::GCStringFromType(static_cast<mem::GCType>(aotHeader->gcType)) << std::endl;
228 stream << " files_count: " << aotHeader->filesCount << std::endl;
229 stream << " files_offset: " << aotHeader->filesOffset << std::endl;
230 stream << " classes_offset: " << aotHeader->classesOffset << std::endl;
231 stream << " methods_offset: " << aotHeader->methodsOffset << std::endl;
232 stream << " bitmap_offset: " << aotHeader->bitmapOffset << std::endl;
233 stream << " strtab_offset: " << aotHeader->strtabOffset << std::endl;
234 }
235
DumpClassHashTable(std::ostream & stream,panda::compiler::AotPandaFile & aotPandaFile,PandaFileHelper & pfile)236 void DumpClassHashTable(std::ostream &stream, panda::compiler::AotPandaFile &aotPandaFile, PandaFileHelper &pfile)
237 {
238 stream << " class_hash_table:" << std::endl;
239 constexpr int ALIGN_SIZE = 32;
240 stream << std::left << std::setfill(' ') << std::setw(ALIGN_SIZE) << "i" << std::left << std::setfill(' ')
241 << std::setw(ALIGN_SIZE) << "next_pos";
242 stream << std::left << std::setfill(' ') << std::setw(ALIGN_SIZE) << "entity_id_offset" << std::left
243 << std::setfill(' ') << std::setw(ALIGN_SIZE) << "descriptor" << std::endl;
244 auto classHashTable = aotPandaFile.GetClassHashTable();
245 auto hashTableSize = classHashTable.size();
246 for (size_t i = 0; i < hashTableSize; i++) {
247 auto entityPair = classHashTable[i];
248 if (entityPair.descriptorHash != 0) {
249 auto descriptor = pfile.GetClassName(entityPair.entityIdOffset);
250 stream << std::left << std::setfill(' ') << std::setw(ALIGN_SIZE) << (i + 1);
251 stream << std::left << std::setfill(' ') << std::dec << std::setw(ALIGN_SIZE) << entityPair.nextPos;
252 stream << std::left << std::setfill(' ') << std::dec << std::setw(ALIGN_SIZE)
253 << entityPair.entityIdOffset;
254 stream << std::left << std::setfill(' ') << std::setw(ALIGN_SIZE) << descriptor << std::endl;
255 } else {
256 stream << std::left << std::setfill(' ') << std::setw(ALIGN_SIZE) << (i + 1) << std::endl;
257 }
258 }
259 }
260
DumpFiles(std::ostream & stream,std::unique_ptr<panda::compiler::AotFile> & aotFile,panda::aoutdump::Options & options)261 void DumpFiles(std::ostream &stream, std::unique_ptr<panda::compiler::AotFile> &aotFile,
262 panda::aoutdump::Options &options)
263 {
264 stream << "files:" << std::endl;
265 for (decltype(auto) fileHeader : aotFile->FileHeaders()) {
266 AotPandaFile aotPandaFile(aotFile.get(), &fileHeader);
267 auto fileName = aotFile->GetString(fileHeader.fileNameStr);
268 PandaFileHelper pfile(fileName);
269 stream << "- name: " << fileName << std::endl;
270 stream << " classes:\n";
271 for (decltype(auto) classHeader : aotPandaFile.GetClassHeaders()) {
272 AotClass klass(aotFile.get(), &classHeader);
273 stream << " - class_id: " << classHeader.classId << std::endl;
274 stream << " name: " << pfile.GetClassName(classHeader.classId) << std::endl;
275 auto methodsBitmap = klass.GetBitmap();
276 BitMemoryRegion rgn(methodsBitmap.data(), methodsBitmap.size());
277 stream << " methods_bitmap: " << rgn << std::endl;
278 stream << " methods:\n";
279 for (decltype(auto) methodHeader : klass.GetMethodHeaders()) {
280 auto methodName = pfile.GetMethodName(methodHeader.methodId);
281 if (options.WasSetMethodRegex() && !methodName.empty()) {
282 static std::regex rgx(options.GetMethodRegex());
283 if (!std::regex_match(methodName, rgx)) {
284 continue;
285 }
286 }
287 stream << " - id: " << std::dec << methodHeader.methodId << std::endl;
288 stream << " name: " << methodName << std::endl;
289 stream << " code_offset: 0x" << std::hex << methodHeader.codeOffset << std::dec << std::endl;
290 stream << " code_size: " << methodHeader.codeSize << std::endl;
291 auto codeInfo = aotPandaFile.GetMethodCodeInfo(&methodHeader);
292 if (options.GetShowCode() == "disasm") {
293 stream << " code: |\n";
294 PrintCode(" ", *aotFile, codeInfo, stream, pfile);
295 }
296 }
297 }
298 DumpClassHashTable(stream, aotPandaFile, pfile);
299 }
300 }
301
PrintCode(const char * prefix,const AotFile & aotFile,const CodeInfo & codeInfo,std::ostream & stream,const PandaFileHelper & pfile) const302 void PrintCode(const char *prefix, const AotFile &aotFile, const CodeInfo &codeInfo, std::ostream &stream,
303 const PandaFileHelper &pfile) const
304 {
305 Arch arch = static_cast<Arch>(aotFile.GetAotHeader()->arch);
306 switch (arch) {
307 #ifdef PANDA_COMPILER_TARGET_AARCH64
308 case Arch::AARCH64:
309 return PrintCodeArm64(prefix, codeInfo, stream, pfile);
310 #endif // PANDA_COMPILER_TARGET_AARCH64
311 #ifdef PANDA_COMPILER_TARGET_X86_64
312 case Arch::X86_64:
313 return PrintCodeX8664(prefix, codeInfo, stream, pfile);
314 #endif // PANDA_COMPILER_TARGET_X86_64
315 default:
316 stream << prefix << "Unsupported target arch: " << GetArchString(arch) << std::endl;
317 break;
318 }
319 }
320
321 #ifdef PANDA_COMPILER_TARGET_AARCH64
PrintCodeArm64(const char * prefix,const CodeInfo & codeInfo,std::ostream & stream,const PandaFileHelper & pfile) const322 void PrintCodeArm64(const char *prefix, const CodeInfo &codeInfo, std::ostream &stream,
323 const PandaFileHelper &pfile) const
324 {
325 Span<const uint8_t> code = codeInfo.GetCodeSpan();
326
327 Decoder decoder(allocator_);
328 Disassembler disasm(allocator_);
329 decoder.AppendVisitor(&disasm);
330 auto startInstr = reinterpret_cast<const Instruction *>(code.data());
331 auto endInstr = reinterpret_cast<const Instruction *>(code.end());
332 uint32_t pc = 0;
333
334 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
335 for (auto instr = startInstr; instr < endInstr; instr += vixl::aarch64::kInstructionSize) {
336 auto stackmap = codeInfo.FindStackMapForNativePc(pc, Arch::AARCH64);
337 if (stackmap.IsValid()) {
338 PrintStackmap(stream, prefix, codeInfo, stackmap, Arch::AARCH64, pfile);
339 }
340 decoder.Decode(instr);
341 stream << prefix << std::hex << std::setw(8U) << std::setfill('0') << instr - startInstr << ": "
342 << disasm.GetOutput() << std::endl;
343 pc += vixl::aarch64::kInstructionSize;
344 }
345 stream << std::dec;
346 }
347 #endif // PANDA_COMPILER_TARGET_AARCH64
348 #ifdef PANDA_COMPILER_TARGET_X86_64
PrintCodeX8664(const char * prefix,const CodeInfo & codeInfo,std::ostream & stream,const PandaFileHelper & pfile) const349 void PrintCodeX8664(const char *prefix, const CodeInfo &codeInfo, std::ostream &stream,
350 const PandaFileHelper &pfile) const
351 {
352 Span<const uint8_t> code = codeInfo.GetCodeSpan();
353 constexpr size_t LENGTH = ZYDIS_MAX_INSTRUCTION_LENGTH; // 15 bytes is max inst length in amd64
354 size_t codeSize = code.size();
355
356 // Initialize decoder context
357 ZydisDecoder decoder;
358 if (!ZYAN_SUCCESS(ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_ADDRESS_WIDTH_64))) {
359 LOG(FATAL, AOT) << "ZydisDecoderInit failed";
360 }
361
362 // Initialize formatter
363 ZydisFormatter formatter;
364 if (!ZYAN_SUCCESS(ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL))) {
365 LOG(FATAL, AOT) << "ZydisFormatterInit failed";
366 }
367
368 for (size_t pos = 0; pos < codeSize;) {
369 ZydisDecodedInstruction instruction;
370 constexpr auto BUF_SIZE = 256;
371 std::array<char, BUF_SIZE> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init)
372 auto len = std::min(LENGTH, codeSize - pos);
373 if (!ZYAN_SUCCESS(ZydisDecoderDecodeBuffer(&decoder, &code[pos], len, &instruction))) {
374 LOG(FATAL, AOT) << "ZydisDecoderDecodeBuffer failed";
375 }
376 if (!ZYAN_SUCCESS(ZydisFormatterFormatInstruction(&formatter, &instruction, buffer.data(), buffer.size(),
377 uintptr_t(&code[pos])))) {
378 LOG(FATAL, AOT) << "ZydisFormatterFormatInstruction failed";
379 }
380 auto stackmap = codeInfo.FindStackMapForNativePc(pos, Arch::X86_64);
381 if (stackmap.IsValid()) {
382 PrintStackmap(stream, prefix, codeInfo, stackmap, Arch::X86_64, pfile);
383 }
384 stream << prefix << std::hex << std::setw(8U) << std::setfill('0') << pos << ": " << buffer.data()
385 << std::endl;
386 pos += instruction.length;
387 }
388 }
389 #endif // PANDA_COMPILER_TARGET_X86_64
390
PrintStackmap(std::ostream & stream,const char * prefix,const CodeInfo & codeInfo,const StackMap & stackmap,Arch arch,const PandaFileHelper & pfile) const391 void PrintStackmap(std::ostream &stream, const char *prefix, const CodeInfo &codeInfo, const StackMap &stackmap,
392 Arch arch, const PandaFileHelper &pfile) const
393 {
394 stream << prefix << " ";
395 codeInfo.Dump(stream, stackmap, arch);
396 stream << std::endl;
397 if (stackmap.HasInlineInfoIndex()) {
398 for (auto ii : const_cast<CodeInfo &>(codeInfo).GetInlineInfos(stackmap)) {
399 stream << prefix << " ";
400 codeInfo.DumpInlineInfo(stream, stackmap, ii.GetRow() - stackmap.GetInlineInfoIndex());
401 auto id =
402 const_cast<CodeInfo &>(codeInfo).GetMethod(stackmap, ii.GetRow() - stackmap.GetInlineInfoIndex());
403 stream << ", method: " << pfile.GetMethodName(std::get<uint32_t>(id));
404 stream << std::endl;
405 }
406 }
407 }
408
409 private:
410 [[maybe_unused]] panda::ArenaAllocator *allocator_;
411 };
412
413 } // namespace panda::aoutdump
414
main(int argc,const char * argv[])415 int main(int argc, const char *argv[])
416 {
417 // NOLINTBEGIN(readability-magic-numbers)
418 panda::mem::MemConfig::Initialize(panda::operator""_MB(64ULL), panda::operator""_MB(64ULL),
419 panda::operator""_MB(64ULL), panda::operator""_MB(32ULL), 0, 0);
420 // NOLINTEND(readability-magic-numbers)
421 panda::PoolManager::Initialize();
422 auto allocator = new panda::ArenaAllocator(panda::SpaceType::SPACE_TYPE_COMPILER);
423 panda::aoutdump::AotDump aotdump(allocator);
424
425 auto result = aotdump.Run(argc, argv);
426
427 delete allocator;
428 panda::PoolManager::Finalize();
429 panda::mem::MemConfig::Finalize();
430 return result;
431 }
432