File indexing completed on 2024-04-28 05:26:53
0001 # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0002 # SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org> 0003 0004 import unittest 0005 from unittest.mock import MagicMock, patch 0006 from chai import Chai 0007 import sys 0008 import os 0009 from pathlib import Path 0010 0011 os.environ['DRKONQI_VERSION'] = '1.2.3' 0012 0013 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 0014 sys.path.append(f'{SCRIPT_DIR}/python/') 0015 sys.path.append(f'{os.path.dirname(SCRIPT_DIR)}/data/') 0016 0017 import gdb_preamble.preamble as preamble 0018 import gdb 0019 0020 class PreambleTest(Chai): 0021 @classmethod 0022 def setUpClass(self): 0023 pass 0024 0025 def setUp(self): 0026 preamble.SentryImage._objfiles = {} 0027 super(PreambleTest, self).setUp() 0028 0029 def frame(self): 0030 self.mock(gdb, 'solib_name') 0031 self.expect(gdb.solib_name).args(0x1).returns( 0032 f'{Path.home()}/foo.so').at_least(0) 0033 0034 symbol = MagicMock() 0035 symbol.__str__.return_value = 'i' 0036 symbol.value.return_value = 123 0037 0038 symtab = MagicMock() 0039 symtab.fullname = MagicMock(return_value=f'{Path.home()}/foo.so') 0040 0041 sal = MagicMock() 0042 sal.symtab = symtab 0043 sal.line = 0 0044 0045 rax = self.mock(MagicMock) 0046 rax.name = "rax" 0047 self.expect(rax, 'startswith').args('ymm').returns(False).at_least(0) 0048 0049 ymm0 = self.mock(MagicMock) 0050 ymm0.name = "ymm0" 0051 self.expect(ymm0, 'startswith').args('ymm').returns(True).at_least(0) 0052 0053 architecture = self.mock() 0054 self.expect(architecture, 'registers').returns(['ymm0', rax]).at_least(0) 0055 0056 rax_value = self.mock() 0057 self.expect(rax_value, 'format_string').args(format='x').returns('0x2').at_least(0) 0058 0059 f = self.mock() 0060 self.expect(f, 'block').returns([symbol]).at_least(0) 0061 self.expect(f, 'find_sal').returns(sal).at_least(0) 0062 self.expect(f, 'name').returns('main').at_least(0) 0063 self.expect(f, 'pc').returns(0x1).at_least(0) 0064 self.expect(f, 'architecture').returns(architecture).at_least(0) 0065 self.expect(f, 'read_register').args(rax).returns(rax_value).at_least(0) 0066 self.expect(f, 'read_register').args(ymm0).times(0) # never 0067 self.expect(f, 'type').returns(0).at_least(0) # NORMAL_FRAME 0068 return f 0069 0070 def test_sentry_variables(self): 0071 variables = preamble.SentryVariables(self.frame()) 0072 self.assert_equal({'i': '123'}, variables.to_dict()) 0073 0074 def test_sentry_frame(self): 0075 sentry_frame = preamble.SentryFrame(self.frame()) 0076 self.assert_equal({'filename': '$HOME/foo.so', 0077 'function': 'main', 0078 'instruction_addr': '0x1', 0079 'lineno': 1, # NOTE: sentry starts at 1, gdb at 0 0080 'package': '$HOME/foo.so', 0081 'vars': {'i': '123'}}, sentry_frame.to_dict()) 0082 0083 def test_sentry_registers(self): 0084 registers = preamble.SentryRegisters(self.frame()) 0085 self.assert_equal({'rax': '0x2'}, registers.to_dict()) 0086 0087 def test_sentry_thread(self): 0088 the_frame = self.frame() 0089 0090 thread = self.mock() 0091 self.expect(thread.switch) 0092 thread.ptid = [123, 456, 789] 0093 thread.name = "TheThread" 0094 self.expect(thread.is_exited).returns(False) 0095 0096 self.mock(gdb, 'newest_frame') 0097 self.expect(gdb.newest_frame).returns(the_frame) 0098 gdb.SIGTRAMP_FRAME = 4 0099 0100 self.mock(gdb, 'FrameIterator') 0101 self.mock(gdb, 'FrameIterator.FrameIterator') 0102 self.expect(gdb.FrameIterator.FrameIterator).args(the_frame).returns([the_frame]) 0103 0104 thread = preamble.SentryThread(thread, is_crashed=False) 0105 self.assert_equal({'crashed': False, 0106 'current': False, 0107 'held_locks': {}, 0108 'state': None, 0109 'id': 456, 0110 'main': False, 0111 'name': 'TheThread', 0112 'stacktrace': {'frames': [{'filename': '$HOME/foo.so', 0113 'function': 'main', 0114 'instruction_addr': '0x1', 0115 'lineno': 1, # NOTE: sentry starts at 1, gdb at 0 0116 'package': '$HOME/foo.so', 0117 'vars': {'i': '123'}}], 0118 'registers': {'rax': '0x2'}}}, thread.to_dict()) 0119 0120 def test_sentry_images(self): 0121 proc_mappings = """ 0122 process 207700 0123 Mapped address spaces: 0124 0125 Start Addr End Addr Size Offset Perms objfile 0126 0x555555554000 0x555555556000 0x2000 0x0 r--p /usr/bin/kwrite 0127 0x555555556000 0x555555558000 0x2000 0x2000 r-xp /usr/bin/kwrite 0128 0x555555558000 0x555555559000 0x1000 0x4000 r--p /usr/bin/kwrite 0129 0x55555555a000 0x55555555b000 0x1000 0x5000 r--p /usr/bin/kwrite 0130 0x55555555b000 0x55555555c000 0x1000 0x6000 rw-p /usr/bin/kwrite 0131 0x55555555c000 0x55555637e000 0xe22000 0x0 rw-p [heap] 0132 0x7fffc0000000 0x7fffc0021000 0x21000 0x0 rw-p 0133 0x7fffc0021000 0x7fffc4000000 0x3fdf000 0x0 ---p 0134 0x7fffc7000000 0x7fffc703c000 0x3c000 0x0 r--p /usr/lib/x86_64-linux-gnu/libx265.so.199 0135 0x7fffc703c000 0x7fffc7ed4000 0xe98000 0x3c000 r-xp /usr/lib/x86_64-linux-gnu/libx265.so.199 0136 0x7fffc7ed4000 0x7fffc7f4f000 0x7b000 0xed4000 r--p /usr/lib/x86_64-linux-gnu/libx265.so.199 0137 0x7fffc7f4f000 0x7fffc7f52000 0x3000 0xf4e000 r--p /usr/lib/x86_64-linux-gnu/libx265.so.199 0138 0x7fffc7f52000 0x7fffc7f55000 0x3000 0xf51000 rw-p /usr/lib/x86_64-linux-gnu/libx265.so.199 0139 0x7fffd5ff6000 0x7fffd5ffc000 0x6000 0x0 r--s /var/cache/fontconfig/68a526f6-7ccc-4a32-af00-647cd10884c1-le64.cache-7 0140 0x7fffdc000000 0x7fffdc001000 0x1000 0x0 r--s /home/me/.local/share/mime/mime.cache 0141 0x7fffdc476000 0x7fffdc676000 0x200000 0x118ded000 rw-s /dev/dri/renderD128 0142 0x7ffff6200000 0x7ffff6228000 0x28000 0x0 r--p /usr/lib/x86_64-linux-gnu/libc.so.6 0143 0x7ffff6228000 0x7ffff63bd000 0x195000 0x28000 r-xp /usr/lib/x86_64-linux-gnu/libc.so.6 0144 0x7ffff63bd000 0x7ffff6415000 0x58000 0x1bd000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6 0145 0x7ffff6415000 0x7ffff6419000 0x4000 0x214000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6 0146 0x7ffff6419000 0x7ffff641b000 0x2000 0x218000 rw-p /usr/lib/x86_64-linux-gnu/libc.so.6 0147 0x7ffff7fbd000 0x7ffff7fc1000 0x4000 0x0 r--p [vvar] 0148 0x7ffff7fc1000 0x7ffff7fc3000 0x2000 0x0 r-xp [vdso] 0149 0x7ffff7fc3000 0x7ffff7fc5000 0x2000 0x0 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0150 0x7ffff7fc5000 0x7ffff7fef000 0x2a000 0x2000 r-xp /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0151 0x7ffff7fef000 0x7ffff7ffa000 0xb000 0x2c000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0152 0x7ffff7ffa000 0x7ffff7ffb000 0x1000 0x10663c000 rw-s /dev/dri/renderD128 0153 0x7ffff7ffb000 0x7ffff7ffd000 0x2000 0x37000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0154 0x7ffff7ffd000 0x7ffff7fff000 0x2000 0x39000 rw-p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0155 0x7ffffffdd000 0x7ffffffff000 0x22000 0x0 rw-p [stack] 0156 0xffffffffff600000 0xffffffffff601000 0x1000 0x0 --xp [vsyscall] 0157 """ 0158 0159 self.mock(gdb, 'execute') 0160 self.expect(gdb.execute).args('info proc mappings', to_string=True).returns(proc_mappings) 0161 0162 obj1 = self.mock() 0163 obj1.filename = '/usr/lib/x86_64-linux-gnu/libc.so.6' 0164 obj1.build_id = '161dd025ad435f0e873aafd55dc65d8a4cb1d93f' 0165 0166 obj2 = self.mock() 0167 obj2.filename = '/usr/lib/x86_64-linux-gnu/libx265.so.199' 0168 obj2.build_id = '272ee025ad435f0e873aafd55dc65d8a4cb1d93f' 0169 0170 self.mock(gdb, 'objfiles') 0171 self.expect(gdb.objfiles).returns([obj1, obj2]) 0172 0173 images = preamble.SentryImages() 0174 self.assert_equal([{'arch': 'x86_64', 0175 'code_file': '/usr/lib/x86_64-linux-gnu/libx265.so.199', 0176 'code_id': '272ee025ad435f0e873aafd55dc65d8a4cb1d93f', 0177 'debug_id': '25e02e27-43ad-0e5f-873a-afd55dc65d8a', 0178 'image_addr': '0x7fffc7000000', 0179 'image_size': 16076800, 0180 'type': 'elf'}, 0181 {'arch': 'x86_64', 0182 'code_file': '/usr/lib/x86_64-linux-gnu/libc.so.6', 0183 'code_id': '161dd025ad435f0e873aafd55dc65d8a4cb1d93f', 0184 'debug_id': '25d01d16-43ad-0e5f-873a-afd55dc65d8a', 0185 'image_addr': '0x7ffff6200000', 0186 'image_size': 2207744, 0187 'type': 'elf'}] 0188 , images.to_list()) 0189 0190 def test_sentry_qml(self): 0191 thread = self.mock() 0192 self.expect(thread.is_valid).returns(True) 0193 self.expect(thread.switch) 0194 0195 inferior = self.mock() 0196 self.expect(inferior.threads).returns([thread]) 0197 0198 symbol0 = self.mock() 0199 symbol0.is_variable = False 0200 symbol0.is_argument = False 0201 0202 frame0 = self.mock(MagicMock) 0203 0204 dereferenced_type = self.mock() 0205 dereferenced_type.name = 'QV4::ExecutionEngine' 0206 0207 typeobj_target = self.mock() 0208 self.expect(typeobj_target.unqualified).returns(dereferenced_type) 0209 0210 typeobj = self.mock() 0211 typeobj.code = 1 0212 self.expect(typeobj.target).returns(typeobj_target) 0213 0214 value1 = MagicMock() 0215 value1.is_optimized_out = False 0216 value1.type = typeobj 0217 value1.__int__.return_value = 0x3 0218 0219 symbol1 = self.mock() 0220 symbol1.is_variable = True 0221 symbol1.is_argument = False 0222 self.expect(symbol1.value).args(frame0).returns(value1) 0223 0224 self.expect(frame0.__len__).returns(5) 0225 self.expect(frame0.block).returns([symbol0, symbol1]) 0226 0227 frame1 = self.mock(MagicMock) 0228 self.expect(frame1.__len__).returns(5) 0229 self.expect(frame1.older).returns(frame0) 0230 0231 self.mock(gdb, 'selected_inferior') 0232 self.expect(gdb.selected_inferior).returns(inferior) 0233 self.mock(gdb, 'selected_thread') 0234 self.expect(gdb.selected_thread).returns(thread) 0235 self.mock(gdb, 'newest_frame') 0236 self.expect(gdb.newest_frame).returns(frame1) 0237 self.mock(gdb, 'parse_and_eval') 0238 qml_trace = ''' 0239 "stack=[frame={level=\\"0\\",func=\\"expression for onCompleted\\",file=\\"qrc:/ui/main.qml\\",fullname=\\"qrc:/ui/main.qml\\",line=\\"10\\",language=\\"js\\"}]" 0240 ''' 0241 self.expect(gdb.parse_and_eval).args('qt_v4StackTraceForEngine((void*)0x3)').returns(qml_trace) 0242 gdb.TYPE_CODE_PTR = 1 0243 0244 thread = preamble.SentryQMLThread() 0245 self.assert_equal({'crashed': True, 0246 'id': 'QML', 0247 'name': 'QML', 0248 'stacktrace': {'frames': [{'filename': 'qrc:/ui/main.qml', 0249 'function': 'expression for onCompleted', 0250 'in_app': True, 0251 'lineno': 10, 0252 'platform': 'other'}]}}, thread.to_dict()) 0253 0254 def test_sentry_image(self): 0255 objfile = self.mock() 0256 objfile.filename = '/usr/bin/true' 0257 objfile.build_id = '272ee025ad435f0e873aafd55dc65d8a4cb1d93f' 0258 0259 self.mock(gdb, 'objfiles') 0260 self.expect(gdb.objfiles).returns([objfile]) 0261 0262 image = preamble.SentryImage('/usr/bin/true', 1000, 2000) 0263 self.assert_equal({'arch': 'x86_64', 0264 'code_file': '/usr/bin/true', 0265 'code_id': '272ee025ad435f0e873aafd55dc65d8a4cb1d93f', 0266 'debug_id': '25e02e27-43ad-0e5f-873a-afd55dc65d8a', 0267 'image_addr': '0x3e8', 0268 'image_size': 1000, 0269 'type': 'elf'}, image.to_dict()) 0270 0271 def test_sentry_image_mapping_fail(self): 0272 try: 0273 import sentry_sdk 0274 0275 objfile = self.mock() 0276 objfile.filename = '/usr/bin/true' 0277 objfile.build_id = '272ee025ad435f0e873aafd55dc65d8a4cb1d93f' 0278 0279 self.mock(gdb, 'objfiles') 0280 self.expect(gdb.objfiles).returns([objfile]) 0281 0282 self.mock(gdb, 'lookup_objfile') 0283 self.expect(gdb.lookup_objfile).returns(None) 0284 0285 self.mock(sentry_sdk, 'capture_exception') 0286 self.expect(sentry_sdk.capture_exception) 0287 preamble.SentryImage('/usr/bin/true.so', 1000, 2000) 0288 except Exception as err: 0289 pass 0290 0291 def test_print_qml_frames(self): 0292 # the payload is missing a line -> we expect no assertions of any kind! 0293 preamble.print_qml_frames('frame={level="0",func="saveConfig",file="/data/projects/kde/usr/share/plasma/shells/org.kde.plasma.desktop/contents/configuration/ConfigurationContainmentAppearance.qml",fullname="/data/project".') 0294 0295 if __name__ == '__main__': 0296 unittest.main() 0297