1 // Copyright 2014 The Crashpad Authors. All rights reserved.
2 //
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 #include "snapshot/mac/mach_o_image_symbol_table_reader.h"
16
17 #include <mach-o/loader.h>
18 #include <mach-o/nlist.h>
19 #include <sys/types.h>
20
21 #include <memory>
22 #include <utility>
23
24 #include "base/logging.h"
25 #include "base/strings/stringprintf.h"
26 #include "util/mac/checked_mach_address_range.h"
27 #include "util/process/process_memory_mac.h"
28
29 namespace crashpad {
30
31 namespace internal {
32
33 //! \brief The internal implementation for MachOImageSymbolTableReader.
34 //!
35 //! Initialization is broken into more than one function that needs to share
36 //! data, so member variables are used. However, much of this data is irrelevant
37 //! after initialization is completed, so rather than doing it in
38 //! MachOImageSymbolTableReader, it’s handled by this class, which is a “friend”
39 //! of MachOImageSymbolTableReader.
40 class MachOImageSymbolTableReaderInitializer {
41 public:
MachOImageSymbolTableReaderInitializer(ProcessReaderMac * process_reader,const MachOImageSegmentReader * linkedit_segment,const std::string & module_info)42 MachOImageSymbolTableReaderInitializer(
43 ProcessReaderMac* process_reader,
44 const MachOImageSegmentReader* linkedit_segment,
45 const std::string& module_info)
46 : module_info_(module_info),
47 linkedit_range_(),
48 process_reader_(process_reader),
49 linkedit_segment_(linkedit_segment) {
50 linkedit_range_.SetRange(process_reader_->Is64Bit(),
51 linkedit_segment->Address(),
52 linkedit_segment->Size());
53 DCHECK(linkedit_range_.IsValid());
54 }
55
~MachOImageSymbolTableReaderInitializer()56 ~MachOImageSymbolTableReaderInitializer() {}
57
58 //! \brief Reads the symbol table from another process.
59 //!
60 //! \sa MachOImageSymbolTableReader::Initialize()
Initialize(const process_types::symtab_command * symtab_command,const process_types::dysymtab_command * dysymtab_command,MachOImageSymbolTableReader::SymbolInformationMap * external_defined_symbols)61 bool Initialize(const process_types::symtab_command* symtab_command,
62 const process_types::dysymtab_command* dysymtab_command,
63 MachOImageSymbolTableReader::SymbolInformationMap*
64 external_defined_symbols) {
65 mach_vm_address_t symtab_address =
66 AddressForLinkEditComponent(symtab_command->symoff);
67 uint32_t symbol_count = symtab_command->nsyms;
68 size_t nlist_size = process_types::nlist::ExpectedSize(process_reader_);
69 mach_vm_size_t symtab_size = symbol_count * nlist_size;
70 if (!IsInLinkEditSegment(symtab_address, symtab_size, "symtab")) {
71 return false;
72 }
73
74 // If a dysymtab is present, use it to filter the symtab for just the
75 // portion used for extdefsym. If no dysymtab is present, the entire symtab
76 // will need to be consulted.
77 uint32_t skip_count = 0;
78 if (dysymtab_command) {
79 if (dysymtab_command->iextdefsym >= symtab_command->nsyms ||
80 dysymtab_command->iextdefsym + dysymtab_command->nextdefsym >
81 symtab_command->nsyms) {
82 LOG(WARNING) << base::StringPrintf(
83 "dysymtab extdefsym %u + %u > symtab nsyms %u",
84 dysymtab_command->iextdefsym,
85 dysymtab_command->nextdefsym,
86 symtab_command->nsyms) << module_info_;
87 return false;
88 }
89
90 skip_count = dysymtab_command->iextdefsym;
91 mach_vm_size_t skip_size = skip_count * nlist_size;
92 symtab_address += skip_size;
93 symtab_size -= skip_size;
94 symbol_count = dysymtab_command->nextdefsym;
95 }
96
97 mach_vm_address_t strtab_address =
98 AddressForLinkEditComponent(symtab_command->stroff);
99 mach_vm_size_t strtab_size = symtab_command->strsize;
100 if (!IsInLinkEditSegment(strtab_address, strtab_size, "strtab")) {
101 return false;
102 }
103
104 std::unique_ptr<process_types::nlist[]> symbols(
105 new process_types::nlist[symtab_command->nsyms]);
106 if (!process_types::nlist::ReadArrayInto(
107 process_reader_, symtab_address, symbol_count, &symbols[0])) {
108 LOG(WARNING) << "could not read symbol table" << module_info_;
109 return false;
110 }
111
112 std::unique_ptr<ProcessMemoryMac::MappedMemory> string_table;
113 for (size_t symbol_index = 0; symbol_index < symbol_count; ++symbol_index) {
114 const process_types::nlist& symbol = symbols[symbol_index];
115 std::string symbol_info = base::StringPrintf(", symbol index %zu%s",
116 skip_count + symbol_index,
117 module_info_.c_str());
118 bool valid_symbol = true;
119 if ((symbol.n_type & N_STAB) == 0 && (symbol.n_type & N_PEXT) == 0 &&
120 (symbol.n_type & N_EXT)) {
121 uint8_t symbol_type = symbol.n_type & N_TYPE;
122 if (symbol_type == N_ABS || symbol_type == N_SECT) {
123 if (symbol.n_strx >= strtab_size) {
124 LOG(WARNING) << base::StringPrintf(
125 "string at 0x%x out of bounds (0x%llx)",
126 symbol.n_strx,
127 strtab_size) << symbol_info;
128 return false;
129 }
130
131 if (!string_table) {
132 string_table = process_reader_->Memory()->ReadMapped(
133 strtab_address, strtab_size);
134 if (!string_table) {
135 LOG(WARNING) << "could not read string table" << module_info_;
136 return false;
137 }
138 }
139
140 std::string name;
141 if (!string_table->ReadCString(symbol.n_strx, &name)) {
142 LOG(WARNING) << "could not read string" << symbol_info;
143 return false;
144 }
145
146 if (symbol_type == N_ABS && symbol.n_sect != NO_SECT) {
147 LOG(WARNING) << base::StringPrintf("N_ABS symbol %s in section %u",
148 name.c_str(),
149 symbol.n_sect) << symbol_info;
150 return false;
151 }
152
153 if (symbol_type == N_SECT && symbol.n_sect == NO_SECT) {
154 LOG(WARNING) << base::StringPrintf(
155 "N_SECT symbol %s in section NO_SECT",
156 name.c_str()) << symbol_info;
157 return false;
158 }
159
160 MachOImageSymbolTableReader::SymbolInformation this_symbol_info;
161 this_symbol_info.value = symbol.n_value;
162 this_symbol_info.section = symbol.n_sect;
163 if (!external_defined_symbols->insert(
164 std::make_pair(name, this_symbol_info)).second) {
165 LOG(WARNING) << "duplicate symbol " << name << symbol_info;
166 return false;
167 }
168 } else {
169 // External indirect symbols may be found in the portion of the symbol
170 // table used for external symbols as opposed to indirect symbols when
171 // the indirect symbols are also external. These can be produced by
172 // Xcode 5.1 ld64-236.3/src/ld/LinkEditClassic.hpp
173 // ld::tool::SymbolTableAtom<>::addGlobal(). Indirect symbols are not
174 // currently supported by this symbol table reader, so ignore them
175 // without failing or logging a message when encountering them. See
176 // https://groups.google.com/a/chromium.org/d/topic/crashpad-dev/k7QkLwO71Zo
177 valid_symbol = symbol_type == N_INDR;
178 }
179 } else {
180 valid_symbol = false;
181 }
182 if (!valid_symbol && dysymtab_command) {
183 LOG(WARNING) << "non-external symbol with type " << symbol.n_type
184 << " in extdefsym" << symbol_info;
185 return false;
186 }
187 }
188
189 return true;
190 }
191
192 private:
193 //! \brief Computes the address for data in the `__LINKEDIT` segment
194 //! identified by its file offset in a Mach-O image.
195 //!
196 //! \param[in] fileoff The file offset relative to the beginning of an image’s
197 //! `mach_header` or `mach_header_64` of the data in the `__LINKEDIT`
198 //! segment.
199 //!
200 //! \return The address, in the remote process’ address space, of the
201 //! requested data.
AddressForLinkEditComponent(uint32_t fileoff) const202 mach_vm_address_t AddressForLinkEditComponent(uint32_t fileoff) const {
203 return linkedit_range_.Base() + fileoff - linkedit_segment_->fileoff();
204 }
205
206 //! \brief Determines whether an address range is located within the
207 //! `__LINKEDIT` segment.
208 //!
209 //! \param[in] address The base address of the range to check.
210 //! \param[in] size The size of the range to check.
211 //! \param[in] tag A string that identifies the range being checked. This is
212 //! used only for logging.
213 //!
214 //! \return `true` if the range identified by \a address + \a size lies
215 //! entirely within the `__LINKEDIT` segment. `false` if that range is
216 //! invalid, or if that range is not contained by the `__LINKEDIT`
217 //! segment, with an appropriate message logged.
IsInLinkEditSegment(mach_vm_address_t address,mach_vm_size_t size,const char * tag) const218 bool IsInLinkEditSegment(mach_vm_address_t address,
219 mach_vm_size_t size,
220 const char* tag) const {
221 CheckedMachAddressRange subrange(process_reader_->Is64Bit(), address, size);
222 if (!subrange.IsValid()) {
223 LOG(WARNING) << base::StringPrintf("invalid %s range (0x%llx + 0x%llx)",
224 tag,
225 address,
226 size) << module_info_;
227 return false;
228 }
229
230 if (!linkedit_range_.ContainsRange(subrange)) {
231 LOG(WARNING) << base::StringPrintf(
232 "%s at 0x%llx + 0x%llx outside of " SEG_LINKEDIT
233 " segment at 0x%llx + 0x%llx",
234 tag,
235 address,
236 size,
237 linkedit_range_.Base(),
238 linkedit_range_.Size()) << module_info_;
239 return false;
240 }
241
242 return true;
243 }
244
245 std::string module_info_;
246 CheckedMachAddressRange linkedit_range_;
247 ProcessReaderMac* process_reader_; // weak
248 const MachOImageSegmentReader* linkedit_segment_; // weak
249
250 DISALLOW_COPY_AND_ASSIGN(MachOImageSymbolTableReaderInitializer);
251 };
252
253 } // namespace internal
254
MachOImageSymbolTableReader()255 MachOImageSymbolTableReader::MachOImageSymbolTableReader()
256 : external_defined_symbols_(), initialized_() {
257 }
258
~MachOImageSymbolTableReader()259 MachOImageSymbolTableReader::~MachOImageSymbolTableReader() {
260 }
261
Initialize(ProcessReaderMac * process_reader,const process_types::symtab_command * symtab_command,const process_types::dysymtab_command * dysymtab_command,const MachOImageSegmentReader * linkedit_segment,const std::string & module_info)262 bool MachOImageSymbolTableReader::Initialize(
263 ProcessReaderMac* process_reader,
264 const process_types::symtab_command* symtab_command,
265 const process_types::dysymtab_command* dysymtab_command,
266 const MachOImageSegmentReader* linkedit_segment,
267 const std::string& module_info) {
268 INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
269
270 internal::MachOImageSymbolTableReaderInitializer initializer(process_reader,
271 linkedit_segment,
272 module_info);
273 if (!initializer.Initialize(
274 symtab_command, dysymtab_command, &external_defined_symbols_)) {
275 return false;
276 }
277
278 INITIALIZATION_STATE_SET_VALID(initialized_);
279 return true;
280 }
281
282 const MachOImageSymbolTableReader::SymbolInformation*
LookUpExternalDefinedSymbol(const std::string & name) const283 MachOImageSymbolTableReader::LookUpExternalDefinedSymbol(
284 const std::string& name) const {
285 INITIALIZATION_STATE_DCHECK_VALID(initialized_);
286
287 const auto& iterator = external_defined_symbols_.find(name);
288 if (iterator == external_defined_symbols_.end()) {
289 return nullptr;
290 }
291 return &iterator->second;
292 }
293
294 } // namespace crashpad
295