Skip to content

Commit 2e7448f

Browse files
committed
[lldb/crashlog] Add support for Application Specific Backtraces & Information
For an exception crashlog, the thread backtraces aren't usually very helpful and instead, developpers look at the "Application Specific Backtrace" that was generated by `objc_exception_throw`. LLDB could already parse and symbolicate these Application Specific Backtraces for regular textual-based crashlog, so this patch adds support to parse them in JSON crashlogs, and materialize them a HistoryThread extending the crashed ScriptedThread. This patch also includes the Application Specific Information messages as part of the process extended crash information log. To do so, the ScriptedProcess Python interface has a new GetMetadata method that returns an arbitrary dictionary with data related to the process. rdar://93207586 Differential Revision: https://reviews.llvm.org/D126260 Signed-off-by: Med Ismail Bennani <[email protected]>
1 parent bd6e0ce commit 2e7448f

File tree

21 files changed

+847
-26
lines changed

21 files changed

+847
-26
lines changed

lldb/examples/python/crashlog.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,12 @@ def parse(self):
462462
self.parse_images(self.data['usedImages'])
463463
self.parse_main_image(self.data)
464464
self.parse_threads(self.data['threads'])
465+
if 'asi' in self.data:
466+
self.crashlog.asi = self.data['asi']
467+
if 'asiBacktraces' in self.data:
468+
self.parse_app_specific_backtraces(self.data['asiBacktraces'])
469+
if 'lastExceptionBacktrace' in self.data:
470+
self.crashlog.asb = self.data['lastExceptionBacktrace']
465471
self.parse_errors(self.data)
466472
thread = self.crashlog.threads[self.crashlog.crashed_thread_idx]
467473
reason = self.parse_crash_reason(self.data['exception'])
@@ -573,6 +579,31 @@ def parse_threads(self, json_threads):
573579
self.crashlog.threads.append(thread)
574580
idx += 1
575581

582+
def parse_asi_backtrace(self, thread, bt):
583+
for line in bt.split('\n'):
584+
frame_match = TextCrashLogParser.frame_regex.search(line)
585+
if not frame_match:
586+
print("error: can't parse application specific backtrace.")
587+
return False
588+
589+
(frame_id, frame_img_name, frame_addr,
590+
frame_ofs) = frame_match.groups()
591+
592+
thread.add_ident(frame_img_name)
593+
if frame_img_name not in self.crashlog.idents:
594+
self.crashlog.idents.append(frame_img_name)
595+
thread.frames.append(self.crashlog.Frame(int(frame_id), int(
596+
frame_addr, 0), frame_ofs))
597+
598+
return True
599+
600+
def parse_app_specific_backtraces(self, json_app_specific_bts):
601+
for idx, backtrace in enumerate(json_app_specific_bts):
602+
thread = self.crashlog.Thread(idx, True)
603+
thread.queue = "Application Specific Backtrace"
604+
if self.parse_asi_backtrace(thread, backtrace):
605+
self.crashlog.threads.append(thread)
606+
576607
def parse_thread_registers(self, json_thread_state, prefix=None):
577608
registers = dict()
578609
for key, state in json_thread_state.items():
@@ -1102,8 +1133,8 @@ def synchronous(debugger):
11021133
run_options.SetEchoCommands(True)
11031134

11041135
commands_stream = lldb.SBStream()
1105-
commands_stream.Print("process status\n")
1106-
commands_stream.Print("thread backtrace\n")
1136+
commands_stream.Print("process status --verbose\n")
1137+
commands_stream.Print("thread backtrace --extended true\n")
11071138
error = debugger.SetInputString(commands_stream.GetData())
11081139
if error.Success():
11091140
debugger.RunCommandInterpreter(True, False, run_options, 0, False, True)

lldb/examples/python/scripted_process/crashlog_scripted_process.py

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ def parse_crashlog(self):
1818
self.crashed_thread_idx = crash_log.crashed_thread_idx
1919
self.loaded_images = []
2020
self.exception = crash_log.exception
21+
self.app_specific_thread = None
22+
if hasattr(crash_log, 'asi'):
23+
self.metadata['asi'] = crash_log.asi
24+
if hasattr(crash_log, 'asb'):
25+
self.extended_thread_info = crash_log.asb
2126

2227
def load_images(self, images):
2328
#TODO: Add to self.loaded_images and load images in lldb
@@ -40,8 +45,23 @@ def load_images(self, images):
4045
for ident in thread.idents:
4146
load_images(self, crash_log.find_images_with_identifier(ident))
4247

48+
if hasattr(thread, 'app_specific_backtrace') and thread.app_specific_backtrace:
49+
# We don't want to include the Application Specific Backtrace
50+
# Thread into the Scripted Process' Thread list.
51+
# Instead, we will try to extract the stackframe pcs from the
52+
# backtrace and inject that as the extended thread info.
53+
self.app_specific_thread = thread
54+
continue
55+
4356
self.threads[thread.index] = CrashLogScriptedThread(self, None, thread)
4457

58+
59+
if self.app_specific_thread:
60+
self.extended_thread_info = \
61+
CrashLogScriptedThread.resolve_stackframes(self.app_specific_thread,
62+
self.addr_mask,
63+
self.target)
64+
4565
def __init__(self, target: lldb.SBTarget, args : lldb.SBStructuredData):
4666
super().__init__(target, args)
4767

@@ -71,6 +91,7 @@ def __init__(self, target: lldb.SBTarget, args : lldb.SBStructuredData):
7191
self.pid = super().get_process_id()
7292
self.crashed_thread_idx = 0
7393
self.exception = None
94+
self.extended_thread_info = None
7495
self.parse_crashlog()
7596

7697
def get_memory_region_containing_address(self, addr: int) -> lldb.SBMemoryRegionInfo:
@@ -103,6 +124,9 @@ def is_alive(self) -> bool:
103124
def get_scripted_thread_plugin(self):
104125
return CrashLogScriptedThread.__module__ + "." + CrashLogScriptedThread.__name__
105126

127+
def get_process_metadata(self):
128+
return self.metadata
129+
106130
class CrashLogScriptedThread(ScriptedThread):
107131
def create_register_ctx(self):
108132
if not self.has_crashed:
@@ -120,21 +144,29 @@ def create_register_ctx(self):
120144

121145
return self.register_ctx
122146

147+
def resolve_stackframes(thread, addr_mask, target):
148+
frames = []
149+
for frame in thread.frames:
150+
frame_pc = frame.pc & addr_mask
151+
pc = frame_pc if frame.index == 0 or frame_pc == 0 else frame_pc - 1
152+
sym_addr = lldb.SBAddress()
153+
sym_addr.SetLoadAddress(pc, target)
154+
if not sym_addr.IsValid():
155+
continue
156+
frames.append({"idx": frame.index, "pc": pc})
157+
return frames
158+
159+
123160
def create_stackframes(self):
124161
if not (self.scripted_process.load_all_images or self.has_crashed):
125162
return None
126163

127164
if not self.backing_thread or not len(self.backing_thread.frames):
128165
return None
129166

130-
for frame in self.backing_thread.frames:
131-
frame_pc = frame.pc & self.scripted_process.addr_mask
132-
pc = frame_pc if frame.index == 0 or frame_pc == 0 else frame_pc - 1
133-
sym_addr = lldb.SBAddress()
134-
sym_addr.SetLoadAddress(pc, self.target)
135-
if not sym_addr.IsValid():
136-
continue
137-
self.frames.append({"idx": frame.index, "pc": pc})
167+
self.frames = CrashLogScriptedThread.resolve_stackframes(self.backing_thread,
168+
self.scripted_process.addr_mask,
169+
self.target)
138170

139171
return self.frames
140172

@@ -144,7 +176,10 @@ def __init__(self, process, args, crashlog_thread):
144176
self.backing_thread = crashlog_thread
145177
self.idx = self.backing_thread.index
146178
self.tid = self.backing_thread.id
147-
self.name = self.backing_thread.name
179+
if self.backing_thread.app_specific_backtrace:
180+
self.name = "Application Specific Backtrace - " + str(self.idx)
181+
else:
182+
self.name = self.backing_thread.name
148183
self.queue = self.backing_thread.queue
149184
self.has_crashed = (self.scripted_process.crashed_thread_idx == self.idx)
150185
self.create_stackframes()
@@ -168,3 +203,9 @@ def get_register_context(self) -> str:
168203
self.register_ctx = self.create_register_ctx()
169204

170205
return struct.pack("{}Q".format(len(self.register_ctx)), *self.register_ctx.values())
206+
207+
def get_extended_info(self):
208+
if (self.has_crashed):
209+
self.extended_info = self.scripted_process.extended_thread_info
210+
return self.extended_info
211+

lldb/examples/python/scripted_process/scripted_process.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class ScriptedProcess(metaclass=ABCMeta):
1818
stack_memory_dump = None
1919
loaded_images = None
2020
threads = None
21+
metadata = None
2122

2223
@abstractmethod
2324
def __init__(self, target, args):
@@ -41,6 +42,7 @@ def __init__(self, target, args):
4142
self.args = args
4243
self.threads = {}
4344
self.loaded_images = []
45+
self.metadata = {}
4446

4547
@abstractmethod
4648
def get_memory_region_containing_address(self, addr):
@@ -138,7 +140,6 @@ def get_process_id(self):
138140
"""
139141
return 0
140142

141-
142143
def launch(self):
143144
""" Simulate the scripted process launch.
144145
@@ -191,6 +192,15 @@ def get_scripted_thread_plugin(self):
191192
"""
192193
return None
193194

195+
def get_process_metadata(self):
196+
""" Get some metadata for the scripted process.
197+
198+
Returns:
199+
Dict: A dictionary containing metadata for the scripted process.
200+
None is the process as no metadata.
201+
"""
202+
return self.metadata
203+
194204
class ScriptedThread(metaclass=ABCMeta):
195205

196206
"""
@@ -226,6 +236,7 @@ def __init__(self, scripted_process, args):
226236
self.register_info = None
227237
self.register_ctx = {}
228238
self.frames = []
239+
self.extended_info = []
229240

230241
if isinstance(scripted_process, ScriptedProcess):
231242
self.target = scripted_process.target
@@ -334,6 +345,15 @@ def get_register_context(self):
334345
"""
335346
pass
336347

348+
def get_extended_info(self):
349+
""" Get scripted thread extended information.
350+
351+
Returns:
352+
List: A list containing the extended information for the scripted process.
353+
None is the thread as no extended information.
354+
"""
355+
return self.extended_info
356+
337357
ARM64_GPR = [ {'name': 'x0', 'bitsize': 64, 'offset': 0, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 0, 'dwarf': 0, 'generic': 'arg0', 'alt-name': 'arg0'},
338358
{'name': 'x1', 'bitsize': 64, 'offset': 8, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 1, 'dwarf': 1, 'generic': 'arg1', 'alt-name': 'arg1'},
339359
{'name': 'x2', 'bitsize': 64, 'offset': 16, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 2, 'dwarf': 2, 'generic': 'arg2', 'alt-name': 'arg2'},

lldb/include/lldb/Interpreter/ScriptedProcessInterface.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ class ScriptedProcessInterface : virtual public ScriptedInterface {
6666
return llvm::None;
6767
}
6868

69+
virtual StructuredData::DictionarySP GetMetadata() { return nullptr; }
70+
6971
protected:
7072
friend class ScriptedThread;
7173
virtual lldb::ScriptedThreadInterfaceSP CreateScriptedThreadInterface() {
@@ -99,6 +101,8 @@ class ScriptedThreadInterface : virtual public ScriptedInterface {
99101
virtual llvm::Optional<std::string> GetRegisterContext() {
100102
return llvm::None;
101103
}
104+
105+
virtual StructuredData::ArraySP GetExtendedInfo() { return nullptr; }
102106
};
103107
} // namespace lldb_private
104108

lldb/include/lldb/Target/Process.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2447,6 +2447,13 @@ void PruneThreadPlans();
24472447
return Status("Not supported");
24482448
}
24492449

2450+
/// Fetch process defined metadata.
2451+
///
2452+
/// \return
2453+
/// A StructuredDataSP object which, if non-empty, will contain the
2454+
/// information related to the process.
2455+
virtual StructuredData::DictionarySP GetMetadata() { return nullptr; }
2456+
24502457
size_t AddImageToken(lldb::addr_t image_ptr);
24512458

24522459
lldb::addr_t GetImagePtrFromToken(size_t token) const;

lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -857,21 +857,20 @@ PlatformDarwin::ParseVersionBuildDir(llvm::StringRef dir) {
857857

858858
llvm::Expected<StructuredData::DictionarySP>
859859
PlatformDarwin::FetchExtendedCrashInformation(Process &process) {
860-
Log *log = GetLog(LLDBLog::Process);
861-
862-
StructuredData::ArraySP annotations = ExtractCrashInfoAnnotations(process);
863-
864-
if (!annotations || !annotations->GetSize()) {
865-
LLDB_LOG(log, "Couldn't extract crash information annotations");
866-
return nullptr;
867-
}
868-
869860
StructuredData::DictionarySP extended_crash_info =
870861
std::make_shared<StructuredData::Dictionary>();
871862

872-
extended_crash_info->AddItem("crash-info annotations", annotations);
863+
StructuredData::ArraySP annotations = ExtractCrashInfoAnnotations(process);
864+
if (annotations && annotations->GetSize())
865+
extended_crash_info->AddItem("Crash-Info Annotations", annotations);
866+
867+
StructuredData::DictionarySP app_specific_info =
868+
ExtractAppSpecificInfo(process);
869+
if (app_specific_info && app_specific_info->GetSize())
870+
extended_crash_info->AddItem("Application Specific Information",
871+
app_specific_info);
873872

874-
return extended_crash_info;
873+
return extended_crash_info->GetSize() ? extended_crash_info : nullptr;
875874
}
876875

877876
StructuredData::ArraySP
@@ -978,6 +977,38 @@ PlatformDarwin::ExtractCrashInfoAnnotations(Process &process) {
978977
return array_sp;
979978
}
980979

980+
StructuredData::DictionarySP
981+
PlatformDarwin::ExtractAppSpecificInfo(Process &process) {
982+
StructuredData::DictionarySP metadata_sp = process.GetMetadata();
983+
984+
if (!metadata_sp || !metadata_sp->GetSize() || !metadata_sp->HasKey("asi"))
985+
return {};
986+
987+
StructuredData::Dictionary *asi;
988+
if (!metadata_sp->GetValueForKeyAsDictionary("asi", asi))
989+
return {};
990+
991+
StructuredData::DictionarySP dict_sp =
992+
std::make_shared<StructuredData::Dictionary>();
993+
994+
auto flatten_asi_dict = [&dict_sp](ConstString key,
995+
StructuredData::Object *val) -> bool {
996+
if (!val)
997+
return false;
998+
999+
StructuredData::Array *arr = val->GetAsArray();
1000+
if (!arr || !arr->GetSize())
1001+
return false;
1002+
1003+
dict_sp->AddItem(key.AsCString(), arr->GetItemAtIndex(0));
1004+
return true;
1005+
};
1006+
1007+
asi->ForEach(flatten_asi_dict);
1008+
1009+
return dict_sp;
1010+
}
1011+
9811012
void PlatformDarwin::AddClangModuleCompilationOptionsForSDKType(
9821013
Target *target, std::vector<std::string> &options, XcodeSDK::Type sdk_type) {
9831014
const std::vector<std::string> apple_arguments = {

lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ class PlatformDarwin : public PlatformPOSIX {
154154
/// \b nullptr if process has no crash information annotations.
155155
StructuredData::ArraySP ExtractCrashInfoAnnotations(Process &process);
156156

157+
/// Extract the `Application Specific Information` messages from a crash
158+
/// report.
159+
StructuredData::DictionarySP ExtractAppSpecificInfo(Process &process);
160+
157161
void ReadLibdispatchOffsetsAddress(Process *process);
158162

159163
void ReadLibdispatchOffsets(Process *process);

lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,19 @@ ScriptedProcess::GetLoadedDynamicLibrariesInfos() {
485485
return loaded_images_sp;
486486
}
487487

488+
lldb_private::StructuredData::DictionarySP ScriptedProcess::GetMetadata() {
489+
CheckInterpreterAndScriptObject();
490+
491+
StructuredData::DictionarySP metadata_sp = GetInterface().GetMetadata();
492+
493+
Status error;
494+
if (!metadata_sp || !metadata_sp->GetSize())
495+
return ScriptedInterface::ErrorWithMessage<StructuredData::DictionarySP>(
496+
LLVM_PRETTY_FUNCTION, "No metadata.", error);
497+
498+
return metadata_sp;
499+
}
500+
488501
ScriptedProcessInterface &ScriptedProcess::GetInterface() const {
489502
return m_interpreter->GetScriptedProcessInterface();
490503
}

lldb/source/Plugins/Process/scripted/ScriptedProcess.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,6 @@ class ScriptedProcess : public Process {
6363

6464
llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
6565

66-
SystemRuntime *GetSystemRuntime() override { return nullptr; }
67-
6866
Status DoLoadCore() override;
6967

7068
Status DoLaunch(Module *exe_module, ProcessLaunchInfo &launch_info) override;
@@ -92,6 +90,8 @@ class ScriptedProcess : public Process {
9290
lldb_private::StructuredData::ObjectSP
9391
GetLoadedDynamicLibrariesInfos() override;
9492

93+
lldb_private::StructuredData::DictionarySP GetMetadata() override;
94+
9595
protected:
9696
Status DoStop();
9797

0 commit comments

Comments
 (0)