LLDB mainline
DebugNamesDWARFIndex.cpp
Go to the documentation of this file.
1//===-- DebugNamesDWARFIndex.cpp ------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
14#include "lldb/Core/Module.h"
16#include "lldb/Utility/Stream.h"
17#include "llvm/ADT/Sequence.h"
18#include <optional>
19
20using namespace lldb_private;
21using namespace lldb;
22using namespace lldb_private::dwarf;
23using namespace lldb_private::plugin::dwarf;
24
25llvm::Expected<std::unique_ptr<DebugNamesDWARFIndex>>
27 DWARFDataExtractor debug_str,
29 auto index_up = std::make_unique<DebugNames>(debug_names.GetAsLLVMDWARF(),
30 debug_str.GetAsLLVM());
31 if (llvm::Error E = index_up->extract())
32 return std::move(E);
33
34 return std::unique_ptr<DebugNamesDWARFIndex>(new DebugNamesDWARFIndex(
35 module, std::move(index_up), debug_names, debug_str, dwarf));
36}
37
38llvm::DenseSet<uint64_t>
40 llvm::DenseSet<uint64_t> result;
41 for (const DebugNames::NameIndex &ni : debug_names) {
42 const uint32_t num_tus = ni.getForeignTUCount();
43 for (uint32_t tu = 0; tu < num_tus; ++tu)
44 result.insert(ni.getForeignTUSignature(tu));
45 }
46 return result;
47}
48
49llvm::DenseSet<dw_offset_t>
51 llvm::DenseSet<dw_offset_t> result;
52 for (const DebugNames::NameIndex &ni : debug_names) {
53 const uint32_t num_cus = ni.getCUCount();
54 for (uint32_t cu = 0; cu < num_cus; ++cu)
55 result.insert(ni.getCUOffset(cu));
56 const uint32_t num_tus = ni.getLocalTUCount();
57 for (uint32_t tu = 0; tu < num_tus; ++tu)
58 result.insert(ni.getLocalTUOffset(tu));
59 }
60 return result;
61}
62
63std::optional<DWARFTypeUnit *>
64DebugNamesDWARFIndex::GetForeignTypeUnit(const DebugNames::Entry &entry) const {
65 std::optional<uint64_t> type_sig = entry.getForeignTUTypeSignature();
66 if (!type_sig.has_value())
67 return std::nullopt;
68
69 // Ask the entry for the skeleton compile unit offset and fetch the .dwo
70 // file from it and get the type unit by signature from there. If we find
71 // the type unit in the .dwo file, we don't need to check that the
72 // DW_AT_dwo_name matches because each .dwo file can have its own type unit.
73 std::optional<uint64_t> cu_offset = entry.getRelatedCUOffset();
74 if (!cu_offset)
75 return nullptr; // Return NULL, this is a type unit, but couldn't find it.
76
77 DWARFUnit *cu =
79 if (!cu)
80 return nullptr; // Return NULL, this is a type unit, but couldn't find it.
81
82 auto dwp_sp = m_debug_info.GetDwpSymbolFile();
83 if (!dwp_sp) {
84 // No .dwp file, we need to load the .dwo file.
85 DWARFUnit &dwo_cu = cu->GetNonSkeletonUnit();
86 // We don't need the check if the type unit matches the .dwo file if we have
87 // a .dwo file (not a .dwp), so we can just return the value here.
88 if (!dwo_cu.IsDWOUnit())
89 return nullptr; // We weren't able to load the .dwo file.
91 *type_sig);
92 }
93 // We have a .dwp file, just get the type unit from there. We need to verify
94 // that the type unit that ended up in the final .dwp file is the right type
95 // unit. Type units have signatures which are the same across multiple .dwo
96 // files, but only one of those type units will end up in the .dwp file. The
97 // contents of type units for the same type can be different in different .dwo
98 // files, which means the DIE offsets might not be the same between two
99 // different type units. So we need to determine if this accelerator table
100 // matches the type unit that ended up in the .dwp file. If it doesn't match,
101 // then we need to ignore this accelerator table entry as the type unit that
102 // is in the .dwp file will have its own index. In order to determine if the
103 // type unit that ended up in a .dwp file matches this DebugNames::Entry, we
104 // need to find the skeleton compile unit for this entry.
105 DWARFTypeUnit *foreign_tu = dwp_sp->DebugInfo().GetTypeUnitForHash(*type_sig);
106 if (!foreign_tu)
107 return nullptr; // Return NULL, this is a type unit, but couldn't find it.
108
109 DWARFBaseDIE cu_die = cu->GetUnitDIEOnly();
110 DWARFBaseDIE tu_die = foreign_tu->GetUnitDIEOnly();
111 llvm::StringRef cu_dwo_name =
112 cu_die.GetAttributeValueAsString(DW_AT_dwo_name, nullptr);
113 llvm::StringRef tu_dwo_name =
114 tu_die.GetAttributeValueAsString(DW_AT_dwo_name, nullptr);
115 if (cu_dwo_name == tu_dwo_name)
116 return foreign_tu; // We found a match!
117 return nullptr; // Return NULL, this is a type unit, but couldn't find it.
118}
119
120DWARFUnit *
121DebugNamesDWARFIndex::GetNonSkeletonUnit(const DebugNames::Entry &entry) const {
122
123 if (std::optional<DWARFTypeUnit *> foreign_tu = GetForeignTypeUnit(entry))
124 return foreign_tu.value();
125
126 // Look for a DWARF unit offset (CU offset or local TU offset) as they are
127 // both offsets into the .debug_info section.
128 std::optional<uint64_t> unit_offset = entry.getCUOffset();
129 if (!unit_offset)
130 unit_offset = entry.getLocalTUOffset();
131 if (unit_offset) {
133 *unit_offset))
134 return &cu->GetNonSkeletonUnit();
135 }
136 return nullptr;
137}
138
139DWARFDIE DebugNamesDWARFIndex::GetDIE(const DebugNames::Entry &entry) const {
140 DWARFUnit *unit = GetNonSkeletonUnit(entry);
141 std::optional<uint64_t> die_offset = entry.getDIEUnitOffset();
142 if (!unit || !die_offset)
143 return DWARFDIE();
144 if (DWARFDIE die = unit->GetDIE(unit->GetOffset() + *die_offset))
145 return die;
146
148 "the DWARF debug information has been modified (bad offset {0:x} in "
149 "debug_names section)\n",
150 *die_offset);
151 return DWARFDIE();
152}
153
155 const DebugNames::Entry &entry,
156 llvm::function_ref<bool(DWARFDIE die)> callback) {
157 DWARFDIE die = GetDIE(entry);
158 if (!die)
159 return true;
160 // Clang used to erroneously emit index entries for declaration DIEs in case
161 // when the definition is in a type unit (llvm.org/pr77696).
162 if (die.IsStructUnionOrClass() &&
163 die.GetAttributeValueAsUnsigned(DW_AT_declaration, 0))
164 return true;
165 return callback(die);
166}
167
169 const DebugNames::NameIndex &ni,
170 llvm::StringRef name) {
171 // Ignore SentinelErrors, log everything else.
174 handleErrors(std::move(error), [](const DebugNames::SentinelError &) {}),
175 "Failed to parse index entries for index at {1:x}, name {2}: {0}",
176 ni.getUnitOffset(), name);
177}
178
180 ConstString basename, llvm::function_ref<bool(DWARFDIE die)> callback) {
181 for (const DebugNames::Entry &entry :
182 m_debug_names_up->equal_range(basename.GetStringRef())) {
183 if (entry.tag() != DW_TAG_variable)
184 continue;
185
186 if (!ProcessEntry(entry, callback))
187 return;
188 }
189
190 m_fallback.GetGlobalVariables(basename, callback);
191}
192
194 const RegularExpression &regex,
195 llvm::function_ref<bool(DWARFDIE die)> callback) {
196 for (const DebugNames::NameIndex &ni: *m_debug_names_up) {
197 for (DebugNames::NameTableEntry nte: ni) {
198 Mangled mangled_name(nte.getString());
199 if (!mangled_name.NameMatches(regex))
200 continue;
201
202 uint64_t entry_offset = nte.getEntryOffset();
203 llvm::Expected<DebugNames::Entry> entry_or = ni.getEntry(&entry_offset);
204 for (; entry_or; entry_or = ni.getEntry(&entry_offset)) {
205 if (entry_or->tag() != DW_TAG_variable)
206 continue;
207
208 if (!ProcessEntry(*entry_or, callback))
209 return;
210 }
211 MaybeLogLookupError(entry_or.takeError(), ni, nte.getString());
212 }
213 }
214
215 m_fallback.GetGlobalVariables(regex, callback);
216}
217
219 DWARFUnit &cu, llvm::function_ref<bool(DWARFDIE die)> callback) {
220 uint64_t cu_offset = cu.GetOffset();
221 bool found_entry_for_cu = false;
222 for (const DebugNames::NameIndex &ni : *m_debug_names_up) {
223 // Check if this name index contains an entry for the given CU.
224 bool cu_matches = false;
225 for (uint32_t i = 0; i < ni.getCUCount(); ++i) {
226 if (ni.getCUOffset(i) == cu_offset) {
227 cu_matches = true;
228 break;
229 }
230 }
231 if (!cu_matches)
232 continue;
233
234 for (DebugNames::NameTableEntry nte : ni) {
235 uint64_t entry_offset = nte.getEntryOffset();
236 llvm::Expected<DebugNames::Entry> entry_or = ni.getEntry(&entry_offset);
237 for (; entry_or; entry_or = ni.getEntry(&entry_offset)) {
238 if (entry_or->tag() != DW_TAG_variable)
239 continue;
240 if (entry_or->getCUOffset() != cu_offset)
241 continue;
242
243 found_entry_for_cu = true;
244 if (!ProcessEntry(*entry_or, callback))
245 return;
246 }
247 MaybeLogLookupError(entry_or.takeError(), ni, nte.getString());
248 }
249 }
250 // If no name index for that particular CU was found, fallback to
251 // creating the manual index.
252 if (!found_entry_for_cu)
253 m_fallback.GetGlobalVariables(cu, callback);
254}
255
257 ConstString class_name, bool must_be_implementation,
258 llvm::function_ref<bool(DWARFDIE die)> callback) {
259 // Keep a list of incomplete types as fallback for when we don't find the
260 // complete type.
261 std::vector<DWARFDIE> incomplete_types;
262
263 for (const DebugNames::Entry &entry :
264 m_debug_names_up->equal_range(class_name.GetStringRef())) {
265 if (entry.tag() != DW_TAG_structure_type &&
266 entry.tag() != DW_TAG_class_type)
267 continue;
268
269 DWARFDIE die = GetDIE(entry);
270 if (!die) {
271 // Report invalid
272 continue;
273 }
274 DWARFUnit *cu = die.GetCU();
276 incomplete_types.push_back(die);
277 continue;
278 }
279
280 if (die.GetAttributeValueAsUnsigned(DW_AT_APPLE_objc_complete_type, 0)) {
281 // If we find the complete version we're done.
282 callback(die);
283 return;
284 }
285 incomplete_types.push_back(die);
286 }
287
288 for (DWARFDIE die : incomplete_types)
289 if (!callback(die))
290 return;
291
292 m_fallback.GetCompleteObjCClass(class_name, must_be_implementation, callback);
293}
294
295namespace {
296using Entry = llvm::DWARFDebugNames::Entry;
297
298/// If `entry` and all of its parents have an `IDX_parent`, use that information
299/// to build and return a list of at most `max_parents` parent Entries.
300/// `entry` itself is not included in the list.
301/// If any parent does not have an `IDX_parent`, or the Entry data is corrupted,
302/// nullopt is returned.
303std::optional<llvm::SmallVector<Entry, 4>>
304getParentChain(Entry entry, uint32_t max_parents) {
305 llvm::SmallVector<Entry, 4> parent_entries;
306
307 do {
308 if (!entry.hasParentInformation())
309 return std::nullopt;
310
311 llvm::Expected<std::optional<Entry>> parent = entry.getParentDIEEntry();
312 if (!parent) {
313 // Bad data.
315 GetLog(DWARFLog::Lookups), parent.takeError(),
316 "Failed to extract parent entry from a non-empty IDX_parent");
317 return std::nullopt;
318 }
319
320 // Last parent in the chain.
321 if (!parent->has_value())
322 break;
323
324 parent_entries.push_back(**parent);
325 entry = **parent;
326 } while (parent_entries.size() < max_parents);
327
328 return parent_entries;
329}
330} // namespace
331
333 const DWARFDeclContext &context,
334 llvm::function_ref<bool(DWARFDIE die)> callback) {
335 if (context.GetSize() == 0)
336 return;
337
338 llvm::StringRef leaf_name = context[0].name;
339 llvm::SmallVector<llvm::StringRef> parent_names;
340 for (auto idx : llvm::seq<int>(1, context.GetSize()))
341 parent_names.emplace_back(context[idx].name);
342
343 // For each entry, grab its parent chain and check if we have a match.
344 for (const DebugNames::Entry &entry :
345 m_debug_names_up->equal_range(leaf_name)) {
346 if (!isType(entry.tag()))
347 continue;
348
349 // If we get a NULL foreign_tu back, the entry doesn't match the type unit
350 // in the .dwp file, or we were not able to load the .dwo file or the DWO ID
351 // didn't match.
352 std::optional<DWARFTypeUnit *> foreign_tu = GetForeignTypeUnit(entry);
353 if (foreign_tu && foreign_tu.value() == nullptr)
354 continue;
355
356 // Grab at most one extra parent, subsequent parents are not necessary to
357 // test equality.
358 std::optional<llvm::SmallVector<Entry, 4>> parent_chain =
359 getParentChain(entry, parent_names.size() + 1);
360
361 if (!parent_chain) {
362 // Fallback: use the base class implementation.
363 if (!ProcessEntry(entry, [&](DWARFDIE die) {
364 return GetFullyQualifiedTypeImpl(context, die, callback);
365 }))
366 return;
367 continue;
368 }
369
370 if (SameParentChain(parent_names, *parent_chain) &&
371 !ProcessEntry(entry, callback))
372 return;
373 }
374 m_fallback.GetFullyQualifiedType(context, callback);
375}
376
378 llvm::ArrayRef<llvm::StringRef> parent_names,
379 llvm::ArrayRef<DebugNames::Entry> parent_entries) const {
380
381 if (parent_entries.size() != parent_names.size())
382 return false;
383
384 auto SameAsEntryATName = [this](llvm::StringRef name,
385 const DebugNames::Entry &entry) {
386 // Peek at the AT_name of `entry` and test equality to `name`.
387 auto maybe_dieoffset = entry.getDIEUnitOffset();
388 if (!maybe_dieoffset)
389 return false;
390 DWARFUnit *unit = GetNonSkeletonUnit(entry);
391 if (!unit)
392 return false;
393 return name == unit->PeekDIEName(unit->GetOffset() + *maybe_dieoffset);
394 };
395
396 // If the AT_name of any parent fails to match the expected name, we don't
397 // have a match.
398 for (auto [parent_name, parent_entry] :
399 llvm::zip_equal(parent_names, parent_entries))
400 if (!SameAsEntryATName(parent_name, parent_entry))
401 return false;
402 return true;
403}
404
406 ConstString name, llvm::function_ref<bool(DWARFDIE die)> callback) {
407 for (const DebugNames::Entry &entry :
408 m_debug_names_up->equal_range(name.GetStringRef())) {
409 if (isType(entry.tag())) {
410 if (!ProcessEntry(entry, callback))
411 return;
412 }
413 }
414
415 m_fallback.GetTypes(name, callback);
416}
417
419 const DWARFDeclContext &context,
420 llvm::function_ref<bool(DWARFDIE die)> callback) {
421 auto name = context[0].name;
422 for (const DebugNames::Entry &entry : m_debug_names_up->equal_range(name)) {
423 if (entry.tag() == context[0].tag) {
424 if (!ProcessEntry(entry, callback))
425 return;
426 }
427 }
428
429 m_fallback.GetTypes(context, callback);
430}
431
433 ConstString name, llvm::function_ref<bool(DWARFDIE die)> callback) {
434 for (const DebugNames::Entry &entry :
435 m_debug_names_up->equal_range(name.GetStringRef())) {
436 lldb_private::dwarf::Tag entry_tag = entry.tag();
437 if (entry_tag == DW_TAG_namespace ||
438 entry_tag == DW_TAG_imported_declaration) {
439 if (!ProcessEntry(entry, callback))
440 return;
441 }
442 }
443
444 m_fallback.GetNamespaces(name, callback);
445}
446
448 const Module::LookupInfo &lookup_info, SymbolFileDWARF &dwarf,
449 const CompilerDeclContext &parent_decl_ctx,
450 llvm::function_ref<bool(DWARFDIE die)> callback) {
451 ConstString name = lookup_info.GetLookupName();
452 std::set<DWARFDebugInfoEntry *> seen;
453 for (const DebugNames::Entry &entry :
454 m_debug_names_up->equal_range(name.GetStringRef())) {
455 Tag tag = entry.tag();
456 if (tag != DW_TAG_subprogram && tag != DW_TAG_inlined_subroutine)
457 continue;
458
459 if (DWARFDIE die = GetDIE(entry)) {
460 if (!ProcessFunctionDIE(lookup_info, die, parent_decl_ctx,
461 [&](DWARFDIE die) {
462 if (!seen.insert(die.GetDIE()).second)
463 return true;
464 return callback(die);
465 }))
466 return;
467 }
468 }
469
470 m_fallback.GetFunctions(lookup_info, dwarf, parent_decl_ctx, callback);
471}
472
474 const RegularExpression &regex,
475 llvm::function_ref<bool(DWARFDIE die)> callback) {
476 for (const DebugNames::NameIndex &ni: *m_debug_names_up) {
477 for (DebugNames::NameTableEntry nte: ni) {
478 if (!regex.Execute(nte.getString()))
479 continue;
480
481 uint64_t entry_offset = nte.getEntryOffset();
482 llvm::Expected<DebugNames::Entry> entry_or = ni.getEntry(&entry_offset);
483 for (; entry_or; entry_or = ni.getEntry(&entry_offset)) {
484 Tag tag = entry_or->tag();
485 if (tag != DW_TAG_subprogram && tag != DW_TAG_inlined_subroutine)
486 continue;
487
488 if (!ProcessEntry(*entry_or, callback))
489 return;
490 }
491 MaybeLogLookupError(entry_or.takeError(), ni, nte.getString());
492 }
493 }
494
495 m_fallback.GetFunctions(regex, callback);
496}
497
499 m_fallback.Dump(s);
500
501 std::string data;
502 llvm::raw_string_ostream os(data);
503 m_debug_names_up->dump(os);
504 s.PutCString(os.str());
505}
static llvm::raw_ostream & error(Stream &strm)
#define LLDB_LOG_ERROR(log, error,...)
Definition: Log.h:382
Represents a generic declaration context in a program.
A uniqued constant string class.
Definition: ConstString.h:40
llvm::StringRef GetStringRef() const
Get the string value as a llvm::StringRef.
Definition: ConstString.h:197
llvm::DWARFDataExtractor GetAsLLVMDWARF() const
llvm::DataExtractor GetAsLLVM() const
A class that handles mangled names.
Definition: Mangled.h:33
bool NameMatches(ConstString name) const
Check if "name" matches either the mangled or demangled name.
Definition: Mangled.h:171
A class that encapsulates name lookup information.
Definition: Module.h:904
ConstString GetLookupName() const
Definition: Module.h:915
A class that describes an executable image and its associated object and symbol files.
Definition: Module.h:88
void ReportErrorIfModifyDetected(const char *format, Args &&...args)
Definition: Module.h:807
bool Execute(llvm::StringRef string, llvm::SmallVectorImpl< llvm::StringRef > *matches=nullptr) const
Execute a regular expression match using the compiled regular expression that is already in this obje...
A stream class that can stream formatted output to a file.
Definition: Stream.h:28
size_t PutCString(llvm::StringRef cstr)
Output a C string to the stream.
Definition: Stream.cpp:65
const char * GetAttributeValueAsString(const dw_attr_t attr, const char *fail_value) const
uint64_t GetAttributeValueAsUnsigned(const dw_attr_t attr, uint64_t fail_value) const
DWARFDIE GetDIE(dw_offset_t die_offset) const
Definition: DWARFDIE.cpp:121
const std::shared_ptr< SymbolFileDWARFDwo > & GetDwpSymbolFile()
DWARFTypeUnit * GetTypeUnitForHash(uint64_t hash)
DWARFUnit * GetUnitAtOffset(DIERef::Section section, dw_offset_t cu_offset, uint32_t *idx_ptr=nullptr)
bool ProcessFunctionDIE(const Module::LookupInfo &lookup_info, DWARFDIE die, const CompilerDeclContext &parent_decl_ctx, llvm::function_ref< bool(DWARFDIE die)> callback)
Helper function implementing common logic for processing function dies.
Definition: DWARFIndex.cpp:26
virtual void GetFullyQualifiedType(const DWARFDeclContext &context, llvm::function_ref< bool(DWARFDIE die)> callback)
Finds all DIEs whose fully qualified name matches context.
Definition: DWARFIndex.cpp:113
bool GetFullyQualifiedTypeImpl(const DWARFDeclContext &context, DWARFDIE die, llvm::function_ref< bool(DWARFDIE die)> callback)
Implementation of GetFullyQualifiedType to check a single entry, shareable with derived classes.
Definition: DWARFIndex.cpp:121
SymbolFileDWARF & GetSymbolFileDWARF() const
Definition: DWARFUnit.h:181
llvm::StringRef PeekDIEName(dw_offset_t die_offset)
Returns the AT_Name of the DIE at die_offset, if it exists, without parsing the entire compile unit.
Definition: DWARFUnit.cpp:673
DWARFDIE GetDIE(dw_offset_t die_offset)
Definition: DWARFUnit.cpp:652
static llvm::Expected< std::unique_ptr< DebugNamesDWARFIndex > > Create(Module &module, DWARFDataExtractor debug_names, DWARFDataExtractor debug_str, SymbolFileDWARF &dwarf)
static llvm::DenseSet< uint64_t > GetTypeUnitSignatures(const DebugNames &debug_names)
static void MaybeLogLookupError(llvm::Error error, const DebugNames::NameIndex &ni, llvm::StringRef name)
DWARFDIE GetDIE(const DebugNames::Entry &entry) const
std::optional< DWARFTypeUnit * > GetForeignTypeUnit(const DebugNames::Entry &entry) const
Checks if an entry is a foreign TU and fetch the type unit.
void GetTypes(ConstString name, llvm::function_ref< bool(DWARFDIE die)> callback) override
void GetFullyQualifiedType(const DWARFDeclContext &context, llvm::function_ref< bool(DWARFDIE die)> callback) override
Uses DWARF5's IDX_parent fields, when available, to speed up this query.
void GetNamespaces(ConstString name, llvm::function_ref< bool(DWARFDIE die)> callback) override
bool ProcessEntry(const DebugNames::Entry &entry, llvm::function_ref< bool(DWARFDIE die)> callback)
void GetGlobalVariables(ConstString basename, llvm::function_ref< bool(DWARFDIE die)> callback) override
Finds global variables with the given base name.
void GetCompleteObjCClass(ConstString class_name, bool must_be_implementation, llvm::function_ref< bool(DWARFDIE die)> callback) override
void GetFunctions(const Module::LookupInfo &lookup_info, SymbolFileDWARF &dwarf, const CompilerDeclContext &parent_decl_ctx, llvm::function_ref< bool(DWARFDIE die)> callback) override
bool SameParentChain(llvm::ArrayRef< llvm::StringRef > parent_names, llvm::ArrayRef< DebugNames::Entry > parent_entries) const
Returns true if parent_entries have identical names to parent_names.
static llvm::DenseSet< dw_offset_t > GetUnits(const DebugNames &debug_names)
DWARFUnit * GetNonSkeletonUnit(const DebugNames::Entry &entry) const
void GetCompleteObjCClass(ConstString class_name, bool must_be_implementation, llvm::function_ref< bool(DWARFDIE die)> callback) override
void GetTypes(ConstString name, llvm::function_ref< bool(DWARFDIE die)> callback) override
void GetNamespaces(ConstString name, llvm::function_ref< bool(DWARFDIE die)> callback) override
void GetFunctions(const Module::LookupInfo &lookup_info, SymbolFileDWARF &dwarf, const CompilerDeclContext &parent_decl_ctx, llvm::function_ref< bool(DWARFDIE die)> callback) override
void GetGlobalVariables(ConstString basename, llvm::function_ref< bool(DWARFDIE die)> callback) override
Finds global variables with the given base name.
A class that represents a running process on the host machine.
Log * GetLog(Cat mask)
Retrieve the Log object for the channel associated with the given log enum.
Definition: Log.h:331
Definition: SBAddress.h:15