| #!/usr/bin/env python3 |
| """ |
| LLDB script to dump the contents of WebKit's IPC message log for testing. |
| |
| Usage in LLDB: |
| (lldb) command script import Tools/Scripts/dump-message-log |
| (lldb) dump_message_log |
| |
| The script dumps messages in reverse chronological order (most recent first), |
| starting from the last written position and stopping when it encounters either: |
| - An Invalid message name (indicating unwritten slots) |
| - A full traversal of the buffer (wrapping back to start) |
| """ |
| |
| import lldb |
| import re |
| |
| def dump_message_log(debugger, command, result, internal_dict): |
| """Dumps the contents of IPC::gMessageLog in reverse chronological order""" |
| |
| target = debugger.GetSelectedTarget() |
| if not target or not target.IsValid(): |
| print("Error: No valid target selected", file=result) |
| return |
| |
| process = target.GetProcess() |
| if not process or not process.IsValid(): |
| print("Error: No valid process attached", file=result) |
| return |
| |
| # Find the global message log |
| buffer_var = target.FindFirstGlobalVariable("IPC::gMessageLog") |
| if not buffer_var or not buffer_var.IsValid(): |
| print("Error: Could not find IPC::gMessageLog", file=result) |
| print("Make sure the process is using a build with the message log compiled in.", file=result) |
| return |
| |
| # Get capacity from the type name (e.g., MessageLog<256>) |
| buffer_type = buffer_var.GetType() |
| type_name = buffer_type.GetName() |
| match = re.search(r'MessageLog<(\d+)>', type_name) |
| if not match: |
| print(f"Error: Could not parse capacity from type name: {type_name}", file=result) |
| return |
| |
| capacity = int(match.group(1)) |
| |
| # Get m_index (std::atomic<size_t>) |
| # Note: m_index is a private member, but LLDB can access it for debugging |
| m_index_var = buffer_var.GetChildMemberWithName("m_index") |
| if not m_index_var or not m_index_var.IsValid(): |
| print("Error: Could not access m_index member", file=result) |
| return |
| |
| # Extract the actual value from std::atomic |
| # Try different member names used by different standard library implementations |
| index_value = None |
| for atomic_member in ["_M_i", "__a_", "_Value", "Value", "__a"]: |
| atomic_storage = m_index_var.GetChildMemberWithName(atomic_member) |
| if atomic_storage and atomic_storage.IsValid(): |
| index_value = atomic_storage.GetValueAsUnsigned() |
| break |
| |
| if index_value is None: |
| # Fallback: try to get value directly |
| index_value = m_index_var.GetValueAsUnsigned() |
| if index_value == 0xFFFFFFFFFFFFFFFF: # Likely failed |
| print("Error: Could not read m_index value", file=result) |
| return |
| |
| # Get m_buffer (std::array) |
| # Note: m_buffer is a private member, but LLDB can access it for debugging |
| m_buffer_var = buffer_var.GetChildMemberWithName("m_buffer") |
| if not m_buffer_var or not m_buffer_var.IsValid(): |
| print("Error: Could not access m_buffer member", file=result) |
| return |
| |
| # std::array may wrap the actual array in an __elems_ or similar member |
| # Try to access it, otherwise use m_buffer_var directly |
| elems_var = m_buffer_var.GetChildMemberWithName("__elems_") |
| if elems_var and elems_var.IsValid(): |
| m_buffer_var = elems_var |
| |
| # Try to find IPC::MessageName::Invalid |
| message_name_type = target.FindFirstType("IPC::MessageName") |
| message_name_invalid = None |
| if message_name_type and message_name_type.IsValid(): |
| enum_members = message_name_type.GetEnumMembers() |
| for i in range(enum_members.GetSize()): |
| member = enum_members.GetTypeEnumMemberAtIndex(i) |
| if member.GetName() == "Invalid": |
| message_name_invalid = member.GetValueAsUnsigned() |
| break |
| |
| # Print header |
| print("\n" + "=" * 80, file=result) |
| print(f"WebKit IPC Message Log Dump (Capacity: {capacity})", file=result) |
| print("=" * 80, file=result) |
| print("\nMessages (most recent first):\n", file=result) |
| |
| # Calculate the last written index |
| # m_index points to the NEXT position to write, so last written is (m_index - 1) % capacity |
| if index_value == 0: |
| last_written = capacity - 1 |
| else: |
| last_written = (index_value - 1) % capacity |
| |
| current_index = last_written |
| messages_found = 0 |
| |
| for i in range(capacity): |
| # Get array element at current_index |
| element = m_buffer_var.GetChildAtIndex(current_index) |
| if not element or not element.IsValid(): |
| print(f"Warning: Could not read buffer[{current_index}]", file=result) |
| break |
| |
| message_value = element.GetValueAsUnsigned() |
| |
| # Stop if we encounter Invalid |
| if message_name_invalid is not None and message_value == message_name_invalid: |
| print(f"\n[Stopped at Invalid message after {messages_found} messages (at buffer[{current_index}])]", file=result) |
| break |
| |
| # Try to get the symbolic name |
| message_name_str = element.GetValue() |
| if message_name_str: |
| # Clean up the format (e.g., "IPCTester_EmptyMessage" instead of full enum path) |
| message_name_str = message_name_str.replace("IPC::MessageName::", "") |
| # Display "Invalid" instead of "Count" since they're semantically invalid messages |
| if message_name_str == "Count": |
| message_name_str = "Invalid" |
| else: |
| message_name_str = f"<value={message_value}>" |
| |
| print(f"{messages_found}. {message_name_str}", file=result) |
| |
| messages_found += 1 |
| |
| # Move to previous index (wrap around) |
| if current_index == 0: |
| current_index = capacity - 1 |
| else: |
| current_index -= 1 |
| |
| print("\n" + "=" * 80 + "\n", file=result) |
| |
| def __lldb_init_module(debugger, internal_dict): |
| """Register the command when the script is loaded""" |
| debugger.HandleCommand( |
| 'command script add -f dump_message_log.dump_message_log dump_message_log' |
| ) |
| print('The "dump_message_log" command has been installed.') |
| |
| if __name__ == "__main__": |
| print("This script is intended to be used within LLDB, not run directly.") |
| print("\nUsage in LLDB:") |
| print(" (lldb) command script import Tools/Scripts/dump-message-log") |
| print(" (lldb) dump_message_log") |