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 "src/trace_processor/importers/elf/binary_info.h"
18
19 #include <fcntl.h>
20
21 #include <cstdint>
22 #include <limits>
23 #include <optional>
24 #include <string>
25
26 #include "perfetto/base/logging.h"
27 #include "perfetto/ext/base/file_utils.h"
28 #include "perfetto/ext/base/scoped_mmap.h"
29 #include "src/trace_processor/importers/elf/elf.h"
30
31 namespace perfetto::trace_processor::elf {
32 namespace {
33
InRange(const void * base,size_t total_size,const void * ptr,size_t size)34 bool InRange(const void* base,
35 size_t total_size,
36 const void* ptr,
37 size_t size) {
38 return ptr >= base && static_cast<const char*>(ptr) + size <=
39 static_cast<const char*>(base) + total_size;
40 }
41
42 template <typename E>
GetElfLoadBias(const void * mem,size_t size)43 std::optional<uint64_t> GetElfLoadBias(const void* mem, size_t size) {
44 const typename E::Ehdr* ehdr = static_cast<const typename E::Ehdr*>(mem);
45 if (!InRange(mem, size, ehdr, sizeof(typename E::Ehdr))) {
46 PERFETTO_ELOG("Corrupted ELF.");
47 return std::nullopt;
48 }
49 for (size_t i = 0; i < ehdr->e_phnum; ++i) {
50 const typename E::Phdr* phdr = GetPhdr<E>(mem, ehdr, i);
51 if (!InRange(mem, size, phdr, sizeof(typename E::Phdr))) {
52 PERFETTO_ELOG("Corrupted ELF.");
53 return std::nullopt;
54 }
55 if (phdr->p_type == PT_LOAD && phdr->p_flags & PF_X) {
56 return phdr->p_vaddr - phdr->p_offset;
57 }
58 }
59 return 0u;
60 }
61
62 template <typename E>
GetElfBuildId(const void * mem,size_t size)63 std::optional<std::string> GetElfBuildId(const void* mem, size_t size) {
64 const typename E::Ehdr* ehdr = static_cast<const typename E::Ehdr*>(mem);
65 if (!InRange(mem, size, ehdr, sizeof(typename E::Ehdr))) {
66 PERFETTO_ELOG("Corrupted ELF.");
67 return std::nullopt;
68 }
69 for (size_t i = 0; i < ehdr->e_shnum; ++i) {
70 const typename E::Shdr* shdr = GetShdr<E>(mem, ehdr, i);
71 if (!InRange(mem, size, shdr, sizeof(typename E::Shdr))) {
72 PERFETTO_ELOG("Corrupted ELF.");
73 return std::nullopt;
74 }
75
76 if (shdr->sh_type != SHT_NOTE)
77 continue;
78
79 auto offset = shdr->sh_offset;
80 while (offset < shdr->sh_offset + shdr->sh_size) {
81 const typename E::Nhdr* nhdr = reinterpret_cast<const typename E::Nhdr*>(
82 static_cast<const char*>(mem) + offset);
83
84 if (!InRange(mem, size, nhdr, sizeof(typename E::Nhdr))) {
85 PERFETTO_ELOG("Corrupted ELF.");
86 return std::nullopt;
87 }
88 if (nhdr->n_type == NT_GNU_BUILD_ID && nhdr->n_namesz == 4) {
89 const char* name = reinterpret_cast<const char*>(nhdr) + sizeof(*nhdr);
90 if (!InRange(mem, size, name, 4)) {
91 PERFETTO_ELOG("Corrupted ELF.");
92 return std::nullopt;
93 }
94 if (memcmp(name, "GNU", 3) == 0) {
95 const char* value = reinterpret_cast<const char*>(nhdr) +
96 sizeof(*nhdr) + base::AlignUp<4>(nhdr->n_namesz);
97
98 if (!InRange(mem, size, value, nhdr->n_descsz)) {
99 PERFETTO_ELOG("Corrupted ELF.");
100 return std::nullopt;
101 }
102 return std::string(value, nhdr->n_descsz);
103 }
104 }
105 offset += sizeof(*nhdr) + base::AlignUp<4>(nhdr->n_namesz) +
106 base::AlignUp<4>(nhdr->n_descsz);
107 }
108 }
109 return std::nullopt;
110 }
111
112 constexpr uint32_t kMachO64Magic = 0xfeedfacf;
113
IsMachO64(const uint8_t * mem,size_t size)114 bool IsMachO64(const uint8_t* mem, size_t size) {
115 if (size < sizeof(kMachO64Magic))
116 return false;
117 return memcmp(mem, &kMachO64Magic, sizeof(kMachO64Magic)) == 0;
118 }
119
120 struct mach_header_64 {
121 uint32_t magic; /* mach magic number identifier */
122 int32_t cputype; /* cpu specifier */
123 int32_t cpusubtype; /* machine specifier */
124 uint32_t filetype; /* type of file */
125 uint32_t ncmds; /* number of load commands */
126 uint32_t sizeofcmds; /* the size of all the load commands */
127 uint32_t flags; /* flags */
128 uint32_t reserved; /* reserved */
129 };
130
131 struct load_command {
132 uint32_t cmd; /* type of load command */
133 uint32_t cmdsize; /* total size of command in bytes */
134 };
135
136 struct segment_64_command {
137 uint32_t cmd; /* LC_SEGMENT_64 */
138 uint32_t cmdsize; /* includes sizeof section_64 structs */
139 char segname[16]; /* segment name */
140 uint64_t vmaddr; /* memory address of this segment */
141 uint64_t vmsize; /* memory size of this segment */
142 uint64_t fileoff; /* file offset of this segment */
143 uint64_t filesize; /* amount to map from the file */
144 uint32_t maxprot; /* maximum VM protection */
145 uint32_t initprot; /* initial VM protection */
146 uint32_t nsects; /* number of sections in segment */
147 uint32_t flags; /* flags */
148 };
149
GetMachOBinaryInfo(const uint8_t * mem,size_t size)150 std::optional<BinaryInfo> GetMachOBinaryInfo(const uint8_t* mem, size_t size) {
151 if (size < sizeof(mach_header_64))
152 return {};
153
154 mach_header_64 header;
155 memcpy(&header, mem, sizeof(mach_header_64));
156
157 if (size < sizeof(mach_header_64) + header.sizeofcmds)
158 return {};
159
160 std::optional<std::string> build_id;
161 uint64_t load_bias = 0;
162
163 const uint8_t* pcmd = mem + sizeof(mach_header_64);
164 const uint8_t* pcmds_end = pcmd + header.sizeofcmds;
165 while (pcmd < pcmds_end) {
166 load_command cmd_header;
167 memcpy(&cmd_header, pcmd, sizeof(load_command));
168
169 constexpr uint32_t LC_SEGMENT_64 = 0x19;
170 constexpr uint32_t LC_UUID = 0x1b;
171
172 switch (cmd_header.cmd) {
173 case LC_UUID: {
174 build_id = std::string(
175 reinterpret_cast<const char*>(pcmd) + sizeof(load_command),
176 cmd_header.cmdsize - sizeof(load_command));
177 break;
178 }
179 case LC_SEGMENT_64: {
180 segment_64_command seg_cmd;
181 memcpy(&seg_cmd, pcmd, sizeof(segment_64_command));
182 if (strcmp(seg_cmd.segname, "__TEXT") == 0) {
183 load_bias = seg_cmd.vmaddr;
184 }
185 break;
186 }
187 default:
188 break;
189 }
190
191 pcmd += cmd_header.cmdsize;
192 }
193
194 if (build_id) {
195 constexpr uint32_t MH_DSYM = 0xa;
196 BinaryType type = header.filetype == MH_DSYM ? BinaryType::kMachODsym
197 : BinaryType::kMachO;
198 return BinaryInfo{*build_id, load_bias, type};
199 }
200 return {};
201 }
202
203 } // namespace
204
IsElf(const uint8_t * mem,size_t size)205 bool IsElf(const uint8_t* mem, size_t size) {
206 if (size <= EI_MAG3)
207 return false;
208 return (mem[EI_MAG0] == ELFMAG0 && mem[EI_MAG1] == ELFMAG1 &&
209 mem[EI_MAG2] == ELFMAG2 && mem[EI_MAG3] == ELFMAG3);
210 }
211
GetBinaryInfo(const uint8_t * mem,size_t size)212 std::optional<BinaryInfo> GetBinaryInfo(const uint8_t* mem, size_t size) {
213 static_assert(EI_MAG3 + 1 == sizeof(kMachO64Magic));
214 static_assert(EI_CLASS > EI_MAG3, "mem[EI_MAG?] accesses are in range.");
215 if (size <= EI_CLASS) {
216 return std::nullopt;
217 }
218 std::optional<std::string> build_id;
219 std::optional<uint64_t> load_bias;
220 if (IsElf(mem, size)) {
221 switch (mem[EI_CLASS]) {
222 case ELFCLASS32:
223 build_id = GetElfBuildId<Elf32>(mem, size);
224 load_bias = GetElfLoadBias<Elf32>(mem, size);
225 break;
226 case ELFCLASS64:
227 build_id = GetElfBuildId<Elf64>(mem, size);
228 load_bias = GetElfLoadBias<Elf64>(mem, size);
229 break;
230 default:
231 return std::nullopt;
232 }
233 if (load_bias) {
234 return BinaryInfo{build_id, *load_bias, BinaryType::kElf};
235 }
236 } else if (IsMachO64(mem, size)) {
237 return GetMachOBinaryInfo(mem, size);
238 }
239 return std::nullopt;
240 }
241
242 } // namespace perfetto::trace_processor::elf
243