1#!/usr/bin/env python3 2# 3# Copyright (C) 2024 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18import unittest 19 20from report_fuchsia import Stacker 21from etm_types import InstrSubtype 22from . test_utils import TestBase 23 24 25class Result: 26 def __init__(self): 27 self.events = [] 28 29 def duration(self, begin, thread, category, name, timestamp): 30 self.events.append(('call' if begin else 'ret', name, timestamp)) 31 32 33class TestEtmStacker(TestBase): 34 def setUp(self): 35 super().setUp() 36 self.r = Result() 37 self.s = Stacker(self.r, 0) 38 39 def assertResult(self, sequence): 40 self.s.flush() 41 self.assertEqual(self.r.events, sequence) 42 43 # Primitives. 44 def test_primitives_call(self): 45 self.s.call('main', 1) 46 self.assertResult([('call', 'main', 1)]) 47 48 def test_primitives_ret(self): 49 self.s.call('main', 1) 50 self.s.ret('main', 2) 51 self.assertResult([('call', 'main', 1), ('ret', 'main', 2)]) 52 53 def test_primitives_no_timestamp(self): 54 self.s.call('main', None) 55 self.s.ret('main', None) 56 self.assertResult([]) 57 58 # Simple stacks. 59 def test_simple_start(self): 60 self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.V8_RET) 61 self.assertResult([('call', 'main', 1), ('ret', 'main', 1)]) 62 63 def test_simple_return(self): 64 self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.V8_RET) 65 self.assertResult([('call', 'main', 1), ('ret', 'main', 1)]) 66 67 def test_simple_inner(self): 68 self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK) 69 self.s.instr_range(2, 'inner', 0, 'inner', 0, InstrSubtype.V8_RET) 70 self.assertResult([('call', 'main', 1), ('call', 'inner', 2), ('ret', 'inner', 2)]) 71 72 def test_simple_recursion(self): 73 self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK) 74 self.s.instr_range(2, 'main', 0, 'main', 0, InstrSubtype.V8_RET) 75 self.s.instr_range(3, 'main', 0, 'main', 0, InstrSubtype.V8_RET) 76 self.assertResult([('call', 'main', 1), ('call', 'main', 2), 77 ('ret', 'main', 2), ('ret', 'main', 3)]) 78 79 def test_simple_tailcall(self): 80 # If not a return or a call, the jump is considered a tail call. 81 self.s.instr_range(1, 'main', 0, 'main', 0, None) 82 self.s.instr_range(2, 'next', 0, 'next', 0, None) 83 self.assertResult([('call', 'main', 1), ('ret', 'main', 2), ('call', 'next', 2)]) 84 85 def test_simple_tailcall_single(self): 86 # If the symbol is different between the start and the end, it changed without a jump. 87 # Pretend that it is a tail-call. 88 self.s.instr_range(1, 'main', 0, 'next', 0, None) 89 self.assertResult([('call', 'main', 1), ('ret', 'main', 1), ('call', 'next', 1)]) 90 91 def test_simple_no_call(self): 92 # If execution returns below the known stack, pretend that we have known about the function 93 # running since the first known timestamp. 94 self.s.instr_range(1, 'main', 0, 'main', 0, None) 95 self.s.instr_range(2, 'main', 0, 'main', 0, InstrSubtype.V8_RET) 96 self.s.instr_range(3, 'next', 0, 'next', 0, None) 97 self.assertResult([('call', 'next', 1), 98 ('call', 'main', 1), ('ret', 'main', 2)]) 99 100 # Delayed events (i.e., no timestamps). 101 def test_delay_later(self): 102 self.s.instr_range(None, 'main', 0, 'main', 0, None) 103 self.s.timestamp(1) 104 self.assertResult([('call', 'main', 1)]) 105 106 def test_delay_later_ret(self): 107 self.s.instr_range(None, 'main', 0, 'main', 0, InstrSubtype.V8_RET) 108 self.s.timestamp(1) 109 self.assertResult([('call', 'main', 1), ('ret', 'main', 1)]) 110 111 def test_delay_inner(self): 112 self.s.instr_range(None, 'main', 0, 'main', 0, InstrSubtype.BR_LINK) 113 self.s.instr_range(2, 'inner', 0, 'inner', 0, InstrSubtype.V8_RET) 114 self.assertResult([('call', 'main', 2), ('call', 'inner', 2), ('ret', 'inner', 2)]) 115 116 def test_delay_no_call(self): 117 self.s.instr_range(None, 'main', 0, 'main', 0, InstrSubtype.V8_RET) 118 self.s.instr_range(2, 'next', 0, 'next', 0, None) 119 self.assertResult([('call', 'next', 2), 120 ('call', 'main', 2), ('ret', 'main', 2)]) 121 122 # Gaps. 123 def test_gaps_gap(self): 124 self.s.instr_range(1, 'main', 0, 'main', 0, None) 125 self.s.gap(2) 126 self.s.instr_range(3, 'next', 0, 'next', 0, None) 127 self.assertResult([('call', 'main', 1), ('ret', 'main', 2), 128 ('call', 'next', 3)]) 129 130 def test_gaps_exception_start(self): 131 self.s.exception(1, 'exception', 0) 132 self.assertResult([('call', 'exception', 1)]) 133 134 def test_gaps_exception_twice(self): 135 self.s.exception(1, 'exception', 0) 136 self.s.gap(2) 137 self.s.exception(2, 'another', 0) 138 self.assertResult([('call', 'exception', 1), ('ret', 'exception', 2), 139 ('call', 'another', 2)]) 140 141 def test_gaps_exception_ret(self): 142 self.s.instr_range(1, 'main', 0, 'main', 0, None) 143 self.s.exception(2, 'exception', 1) 144 self.s.gap(2) 145 self.s.instr_range(3, 'main', 1, 'main', 1, InstrSubtype.V8_RET) 146 self.assertResult([('call', 'main', 1), ('call', 'exception', 2), 147 ('ret', 'exception', 3), ('ret', 'main', 3)]) 148 149 def test_gaps_exception_ret_twice(self): 150 self.s.instr_range(1, 'main', 0, 'main', 0, None) 151 self.s.exception(2, 'exception', 1) 152 self.s.gap(2) 153 self.s.exception(3, 'another', 1) 154 self.s.gap(3) 155 self.s.instr_range(4, 'main', 1, 'main', 1, InstrSubtype.V8_RET) 156 self.assertResult([('call', 'main', 1), ('call', 'exception', 2), 157 ('ret', 'exception', 3), ('call', 'another', 3), 158 ('ret', 'another', 4), ('ret', 'main', 4)]) 159 160 def test_gaps_exception_ret_bad(self): 161 self.s.instr_range(1, 'main', 0, 'main', 0, None) 162 self.s.exception(2, 'exception', 100) 163 self.s.gap(2) 164 self.s.instr_range(3, 'next', 1, 'next', 1, None) 165 self.assertResult([('call', 'main', 1), ('call', 'exception', 2), 166 ('ret', 'exception', 3), ('ret', 'main', 3), 167 ('call', 'next', 3)]) 168 169 def test_gaps_exception_ret_twice_bad(self): 170 self.s.instr_range(1, 'main', 0, 'main', 0, None) 171 self.s.exception(2, 'exception', 1) 172 self.s.gap(2) 173 self.s.exception(3, 'another', 2) 174 self.s.gap(3) 175 self.s.instr_range(4, 'next', 3, 'next', 3, None) 176 self.assertResult([('call', 'main', 1), ('call', 'exception', 2), 177 ('ret', 'exception', 3), ('ret', 'main', 3), 178 ('call', 'another', 3), ('ret', 'another', 4), ('call', 'next', 4)]) 179 180 def test_gaps_exception_call_preserved(self): 181 self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK) 182 self.s.exception(2, 'exception', 1) 183 self.s.gap(2) 184 self.s.instr_range(3, 'next', 1, 'next', 2, None) 185 self.assertResult([('call', 'main', 1), ('call', 'exception', 2), 186 ('ret', 'exception', 3), ('call', 'next', 3)]) 187 188 def test_gaps_exception_ret_preserved(self): 189 # The actual ret is not preserved, but the starting point of elements below the known stack 190 # should be. 191 self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.V8_RET) 192 self.s.exception(2, 'exception', 1) 193 self.s.gap(2) 194 self.s.instr_range(3, 'next', 1, 'next', 2, None) 195 self.assertResult([('call', 'next', 1), 196 ('call', 'main', 1), ('ret', 'main', 1), 197 ('call', 'exception', 2), ('ret', 'exception', 3)]) 198 199 def test_gaps_plt(self): 200 self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK) 201 self.s.instr_range(2, 'func@plt', 0, 'func@plt', 0, None) 202 self.s.gap(3) 203 self.s.instr_range(4, 'main', 0, 'main', 0, None) 204 self.s.instr_range(5, 'main', 0, 'main', 0, InstrSubtype.V8_RET) 205 self.assertResult([('call', 'main', 1), ('call', 'func@plt', 2), 206 ('ret', 'func@plt', 4), ('ret', 'main', 5)]) 207 208 def test_gaps_plt_wrong(self): 209 self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK) 210 self.s.instr_range(2, 'func@plt', 0, 'func@plt', 0, None) 211 self.s.gap(3) 212 self.s.instr_range(4, 'next', 0, 'next', 0, None) 213 self.assertResult([('call', 'main', 1), ('call', 'func@plt', 2), 214 ('ret', 'func@plt', 4), ('ret', 'main', 4), 215 ('call', 'next', 4)]) 216 217 def test_gaps_danger(self): 218 self.s.instr_range(1, 'main', 0, 'main', 0, None) 219 self.s.exception(2, 'exception', 100) 220 self.s.gap(2) 221 self.s.instr_range(3, 'main', 0, 'main', 0, InstrSubtype.V8_RET) 222 self.assertResult([('call', 'main', 1), ('call', 'exception', 2), 223 ('ret', 'exception', 3), ('ret', 'main', 3)]) 224 225 # Stack was lost. 226 def test_lost_lost(self): 227 self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK) 228 self.s.instr_range(2, 'next', 0, 'next', 0, None) 229 self.s.lost_stack(3) 230 self.assertResult([('call', 'main', 1), ('call', 'next', 2), 231 ('ret', 'next', 3), ('ret', 'main', 3)]) 232 233 def test_lost_ordering(self): 234 self.s.exception(1, 'exception', 0) 235 self.s.gap(None) 236 self.s.lost_stack(None) 237 self.s.timestamp(2) 238 self.assertResult([('call', 'exception', 1), ('ret', 'exception', 2)]) 239 240 def test_lost_assumption(self): 241 self.s.instr_range(1, 'main', 0, 'main', 0, None) 242 self.s.exception(2, 'exception', 0) 243 self.s.gap(2) 244 self.s.instr_range(3, 'main', 0, 'main', 0, None) 245 self.assertResult([('call', 'main', 1), ('call', 'exception', 2), 246 ('ret', 'exception', 3)]) 247 248 def test_lost_late(self): 249 self.s.instr_range(None, 'main', 0, 'main', 0, InstrSubtype.BR_LINK) 250 self.s.exception(None, 'exception', 0) 251 self.s.gap(None) 252 self.s.instr_range(1, 'next', 0, 'next', 0, None) 253 self.assertResult([('call', 'main', 1), ('call', 'exception', 1), ('ret', 'exception', 1), 254 ('call', 'next', 1)]) 255 256 # Timestamps. 257 def test_timestamps_updates(self): 258 self.s.timestamp(10) 259 self.assertEqual(self.s.last_timestamp, 10) 260 261 def test_timestamps_gap_removes(self): 262 self.s.timestamp(10) 263 self.s.gap(11) 264 self.assertEqual(self.s.last_timestamp, None) 265 266 def test_timestamps_gap_removes_exception(self): 267 self.s.timestamp(10) 268 self.s.exception(10, "exception", 0) 269 self.s.gap(10) 270 self.assertEqual(self.s.last_timestamp, None) 271 272 def test_timestamps_gap_removes_plt(self): 273 self.s.instr_range(1, 'foo@plt', 0, 'foo@plt', 0, None) 274 self.s.gap(10) 275 self.assertEqual(self.s.last_timestamp, None) 276 277 def test_timestamps_gap_again(self): 278 self.s.timestamp(10) 279 self.s.gap(11) 280 self.s.timestamp(11) 281 self.assertEqual(self.s.last_timestamp, 11) 282 283 def test_timestamps_first(self): 284 self.s.instr_range(1, 'main', 0, 'main', 0, None) 285 self.s.instr_range(2, 'main', 0, 'main', 0, None) 286 self.assertEqual(self.s.first_timestamp, 1) 287 self.assertEqual(self.s.last_timestamp, 2) 288 289 def test_timestamps_first_gap(self): 290 self.s.instr_range(1, 'main', 0, 'main', 0, None) 291 self.s.gap(10) 292 self.s.instr_range(11, 'main', 0, 'main', 0, None) 293 self.assertEqual(self.s.first_timestamp, 11) 294 295 def test_timestamps_first_exception_good(self): 296 self.s.instr_range(1, 'main', 0, 'main', 0, None) 297 self.s.exception(2, 'exception', 100) 298 self.s.gap(10) 299 self.s.instr_range(11, 'main', 100, 'main', 100, None) 300 self.assertEqual(self.s.first_timestamp, 1) 301 302 def test_timestamps_first_exception_bad(self): 303 self.s.instr_range(1, 'main', 0, 'main', 0, None) 304 self.s.exception(2, 'exception', 100) 305 self.s.gap(10) 306 self.s.instr_range(11, 'next', 200, 'next', 200, None) 307 self.assertEqual(self.s.first_timestamp, 11) 308 309 def test_timestamps_first_plt_good(self): 310 self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK) 311 self.s.instr_range(2, 'foo@plt', 0, 'foo@plt', 0, None) 312 self.s.gap(10) 313 self.s.instr_range(11, 'main', 200, 'main', 200, None) 314 self.assertEqual(self.s.first_timestamp, 1) 315 316 def test_timestamps_first_plt_bad(self): 317 self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK) 318 self.s.instr_range(2, 'foo@plt', 0, 'foo@plt', 0, None) 319 self.s.gap(10) 320 self.s.instr_range(11, 'next', 200, 'next', 200, None) 321 self.assertEqual(self.s.first_timestamp, 11) 322 323 def test_timestamps_exception_fake_start(self): 324 self.s.exception(1, 'exception', 100) 325 self.s.lost_stack(None) 326 self.s.instr_range(None, 'main', 0, 'main', 0, InstrSubtype.V8_RET) 327 self.s.instr_range(2, 'deep', 0, 'deep', 0, None) 328 self.assertResult([('call', 'exception', 1), ('ret', 'exception', 2), 329 ('call', 'deep', 2), ('call', 'main', 2), ('ret', 'main', 2)]) 330