1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <stdio.h>
18 
19 #include <memory>
20 #include <regex>
21 #include <string>
22 
23 #include "ETMDecoder.h"
24 #include "command.h"
25 #include "record_file.h"
26 #include "thread_tree.h"
27 #include "utils.h"
28 
29 using namespace simpleperf;
30 
31 namespace {
32 
33 using AddrPair = std::pair<uint64_t, uint64_t>;
34 
35 struct AddrPairHash {
36   size_t operator()(const AddrPair& ap) const noexcept {
37     size_t seed = 0;
38     HashCombine(seed, ap.first);
39     HashCombine(seed, ap.second);
40     return seed;
41   }
42 };
43 
44 struct BinaryInfo {
45   std::unordered_map<AddrPair, uint64_t, AddrPairHash> range_count_map;
46   std::unordered_map<AddrPair, uint64_t, AddrPairHash> branch_count_map;
47 };
48 
49 class InjectCommand : public Command {
50  public:
51   InjectCommand()
52       : Command("inject", "convert etm instruction tracing data into instr ranges",
53                 // clang-format off
54 "Usage: simpleperf inject [options]\n"
55 "--binary binary_name         Generate data only for binaries matching binary_name regex.\n"
56 "-i <file>                    input perf.data, generated by recording cs-etm event type.\n"
57 "                             Default is perf.data.\n"
58 "-o <file>                    output file. Default is perf_inject.data.\n"
59 "                             The output is in text format accepted by AutoFDO.\n"
60 "--dump-etm type1,type2,...   Dump etm data. A type is one of raw, packet and element.\n"
61 "--symdir <dir>               Look for binaries in a directory recursively.\n"
62                 // clang-format on
63                 ),
64         output_fp_(nullptr, fclose) {}
65 
66   bool Run(const std::vector<std::string>& args) override {
67     if (!ParseOptions(args)) {
68       return false;
69     }
70     record_file_reader_ = RecordFileReader::CreateInstance(input_filename_);
71     if (!record_file_reader_) {
72       return false;
73     }
74     record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
75     output_fp_.reset(fopen(output_filename_.c_str(), "w"));
76     if (!output_fp_) {
77       PLOG(ERROR) << "failed to write to " << output_filename_;
78       return false;
79     }
80     if (!record_file_reader_->ReadDataSection([this](auto r) { return ProcessRecord(r.get()); })) {
81       return false;
82     }
83     if (etm_decoder_ && !etm_decoder_->FinishData()) {
84       return false;
85     }
86     PostProcess();
87     output_fp_.reset(nullptr);
88     return true;
89   }
90 
91  private:
92   bool ParseOptions(const std::vector<std::string>& args) {
93     for (size_t i = 0; i < args.size(); i++) {
94       if (args[i] == "--binary") {
95         if (!NextArgumentOrError(args, &i)) {
96           return false;
97         }
98         binary_name_regex_ = args[i];
99       } else if (args[i] == "-i") {
100         if (!NextArgumentOrError(args, &i)) {
101           return false;
102         }
103         input_filename_ = args[i];
104       } else if (args[i] == "-o") {
105         if (!NextArgumentOrError(args, &i)) {
106           return false;
107         }
108         output_filename_ = args[i];
109       } else if (args[i] == "--dump-etm") {
110         if (!NextArgumentOrError(args, &i) || !ParseEtmDumpOption(args[i], &etm_dump_option_)) {
111           return false;
112         }
113       } else if (args[i] == "--symdir") {
114         if (!NextArgumentOrError(args, &i) || !Dso::AddSymbolDir(args[i])) {
115           return false;
116         }
117       } else {
118         ReportUnknownOption(args, i);
119         return false;
120       }
121     }
122     return true;
123   }
124 
125   bool ProcessRecord(Record* r) {
126     thread_tree_.Update(*r);
127     if (r->type() == PERF_RECORD_AUXTRACE_INFO) {
128       auto instr_range_callback = [this](auto& range) { ProcessInstrRange(range); };
129       etm_decoder_ = ETMDecoder::Create(*static_cast<AuxTraceInfoRecord*>(r), thread_tree_);
130       if (!etm_decoder_) {
131         return false;
132       }
133       etm_decoder_->EnableDump(etm_dump_option_);
134       etm_decoder_->RegisterCallback(instr_range_callback);
135     } else if (r->type() == PERF_RECORD_AUX) {
136       AuxRecord* aux = static_cast<AuxRecord*>(r);
137       uint64_t aux_size = aux->data->aux_size;
138       if (aux_size > 0) {
139         if (aux_data_buffer_.size() < aux_size) {
140           aux_data_buffer_.resize(aux_size);
141         }
142         if (!record_file_reader_->ReadAuxData(aux->Cpu(), aux->data->aux_offset,
143                                               aux_data_buffer_.data(), aux_size)) {
144           LOG(ERROR) << "failed to read aux data";
145           return false;
146         }
147         return etm_decoder_->ProcessData(aux_data_buffer_.data(), aux_size);
148       }
149     }
150     return true;
151   }
152 
153   std::unordered_map<Dso*, bool> dso_filter_cache;
154   bool FilterInstrRange(const ETMInstrRange& instr_range) {
155     auto lookup = dso_filter_cache.find(instr_range.dso);
156     if (lookup != dso_filter_cache.end()) {
157       return lookup->second;
158     }
159     bool match = std::regex_search(instr_range.dso->GetDebugFilePath(),
160                                    binary_name_regex_);
161     dso_filter_cache.insert({instr_range.dso, match});
162     return match;
163   }
164 
165   void ProcessInstrRange(const ETMInstrRange& instr_range) {
166     if (!FilterInstrRange(instr_range)) {
167       return;
168     }
169 
170     auto& binary = binary_map_[instr_range.dso];
171     binary.range_count_map[AddrPair(instr_range.start_addr, instr_range.end_addr)] +=
172         instr_range.branch_taken_count + instr_range.branch_not_taken_count;
173     if (instr_range.branch_taken_count > 0) {
174       binary.branch_count_map[AddrPair(instr_range.end_addr, instr_range.branch_to_addr)] +=
175           instr_range.branch_taken_count;
176     }
177   }
178 
179   void PostProcess() {
180     // binary_map is used to store instruction ranges, which can have a large amount. And it has
181     // a larger access time (instruction ranges * executed time). So it's better to use
182     // unorder_maps to speed up access time. But we also want a stable output here, to compare
183     // output changes result from code changes. So generate a sorted output here.
184     std::vector<Dso*> dso_v;
185     for (auto& p : binary_map_) {
186       dso_v.emplace_back(p.first);
187     }
188     std::sort(dso_v.begin(), dso_v.end(), [](Dso* d1, Dso* d2) { return d1->Path() < d2->Path(); });
189     for (auto dso : dso_v) {
190       const BinaryInfo& binary = binary_map_[dso];
191 
192       // Write range_count_map.
193       std::map<AddrPair, uint64_t> range_count_map(binary.range_count_map.begin(),
194                                                    binary.range_count_map.end());
195       fprintf(output_fp_.get(), "%zu\n", range_count_map.size());
196       for (const auto& pair2 : range_count_map) {
197         const AddrPair& addr_range = pair2.first;
198         uint64_t count = pair2.second;
199 
200         fprintf(output_fp_.get(), "%" PRIx64 "-%" PRIx64 ":%" PRIu64 "\n", addr_range.first,
201                 addr_range.second, count);
202       }
203 
204       // Write addr_count_map.
205       fprintf(output_fp_.get(), "0\n");
206 
207       // Write branch_count_map.
208       std::map<AddrPair, uint64_t> branch_count_map(binary.branch_count_map.begin(),
209                                                     binary.branch_count_map.end());
210       fprintf(output_fp_.get(), "%zu\n", branch_count_map.size());
211       for (const auto& pair2 : branch_count_map) {
212         const AddrPair& branch = pair2.first;
213         uint64_t count = pair2.second;
214 
215         fprintf(output_fp_.get(), "%" PRIx64 "->%" PRIx64 ":%" PRIu64 "\n", branch.first,
216                 branch.second, count);
217       }
218 
219       // Write the binary path in comment.
220       fprintf(output_fp_.get(), "// %s\n\n", dso->Path().c_str());
221     }
222   }
223 
224   std::regex binary_name_regex_{""};  // Default to match everything.
225   std::string input_filename_ = "perf.data";
226   std::string output_filename_ = "perf_inject.data";
227   ThreadTree thread_tree_;
228   std::unique_ptr<RecordFileReader> record_file_reader_;
229   ETMDumpOption etm_dump_option_;
230   std::unique_ptr<ETMDecoder> etm_decoder_;
231   std::vector<uint8_t> aux_data_buffer_;
232   std::unique_ptr<FILE, decltype(&fclose)> output_fp_;
233 
234   // Store results for AutoFDO.
235   std::unordered_map<Dso*, BinaryInfo> binary_map_;
236 };
237 
238 }  // namespace
239 
240 void RegisterInjectCommand() {
241   return RegisterCommand("inject", [] { return std::unique_ptr<Command>(new InjectCommand); });
242 }
243