From 6f07d759c2d593e3c72d20c6420742dbc8beaeb3 Mon Sep 17 00:00:00 2001 From: lishuaichao Date: Mon, 8 Dec 2025 18:49:28 +0800 Subject: [PATCH 1/6] fix symbolization error in iOS 26 --- btrace-iOS/BTraceTool/btrace/app.py | 2 +- btrace-iOS/BTraceTool/btrace/cli.py | 19 +- btrace-iOS/BTraceTool/btrace/extractor.py | 469 +++++++++++++++++++++- btrace-iOS/BTraceTool/btrace/model.py | 65 ++- btrace-iOS/BTraceTool/btrace/parse.py | 62 ++- btrace-iOS/BTraceTool/btrace/perfetto.py | 20 +- btrace-iOS/BTraceTool/btrace/util.py | 109 +++-- btrace-iOS/BTraceTool/pyproject.toml | 2 +- 8 files changed, 676 insertions(+), 72 deletions(-) diff --git a/btrace-iOS/BTraceTool/btrace/app.py b/btrace-iOS/BTraceTool/btrace/app.py index 05a48f9..524ec14 100644 --- a/btrace-iOS/BTraceTool/btrace/app.py +++ b/btrace-iOS/BTraceTool/btrace/app.py @@ -95,7 +95,7 @@ def stop_record() -> Optional[bytes]: url = util.API_HOST + "/record/stop" config = {} - resp = requests.post(url, json=config, timeout=30) + resp = requests.post(url, json=config, timeout=60) resp.raise_for_status() gz_data = resp.content diff --git a/btrace-iOS/BTraceTool/btrace/cli.py b/btrace-iOS/BTraceTool/btrace/cli.py index c35897d..f4a884c 100644 --- a/btrace-iOS/BTraceTool/btrace/cli.py +++ b/btrace-iOS/BTraceTool/btrace/cli.py @@ -79,6 +79,7 @@ def cmd_record(args): device_id = args.device_id bundle_id = args.bundle_id + frequency = args.frequency time_limit = args.time_limit or 3600 hitch = args.hitch or 50 hitch = max(hitch, 33) @@ -86,8 +87,12 @@ def cmd_record(args): launch = args.launch next_launch = args.next_launch verbose = args.verbose + upload = args.upload util.set_verbose(verbose) + + if util.verbose(): + print("args: ", args) device_info = {} @@ -164,7 +169,7 @@ def cmd_record(args): pid = device.connect_if_need(device_id) pid_list.append(pid) - util.set_record_parameter(main_thread_only, hitch) + util.set_record_parameter(main_thread_only, hitch, frequency) if launch: app.launch(device_id, bundle_id) @@ -188,7 +193,7 @@ def signal_stop(signum, frame): signal.signal(signal.SIGINT, signal_stop) - print(f"Press cltr+c to stop") + print(f"Press ctrl+c to stop") interval = 0.5 record_time = 0 @@ -218,10 +223,11 @@ def cmd_parse(args): sys_symbol = args.sys_symbol force = args.force verbose = args.verbose + upload = args.upload util.set_verbose(verbose) - parse(file_path, dsym_path, force, sys_symbol) + parse(file_path, dsym_path, force, sys_symbol, upload) def cmd_export(args): @@ -274,6 +280,7 @@ def main(): "-H", "--hitch", dest="hitch", type=int, help="hitch thres" ) record_parser.add_argument("-d", "--dsym_path", dest="dsym_path", help="dsym dir") + record_parser.add_argument("-f", "--frequency", dest="frequency", help="frequency, e.g., 1ms, 100us") record_parser.add_argument( "-m", "--main_thread_only", @@ -293,6 +300,9 @@ def main(): record_parser.add_argument( "-v", "--verbose", dest="verbose", action="store_true", help="show verbose info" ) + record_parser.add_argument( + "-u", "--upload", dest="upload", action="store_true", help="upload trace file" + ) # parse parser_lib = subparser.add_parser("parse", help="parse trace data") @@ -307,6 +317,9 @@ def main(): parser_lib.add_argument( "-v", "--verbose", dest="verbose", action="store_true", help="show verbose info" ) + parser_lib.add_argument( + "-u", "--upload", dest="upload", action="store_true", help="upload trace file" + ) # export export_lib = subparser.add_parser("export", help="export trace data") diff --git a/btrace-iOS/BTraceTool/btrace/extractor.py b/btrace-iOS/BTraceTool/btrace/extractor.py index a7cd62c..981ca72 100644 --- a/btrace-iOS/BTraceTool/btrace/extractor.py +++ b/btrace-iOS/BTraceTool/btrace/extractor.py @@ -28,6 +28,7 @@ ImageInfo, IntervalSampleNode, LockAction, + LockContentionPair, LockSample, LockSampleNode, MemSample, @@ -41,6 +42,8 @@ TimeRangeSample, TimeRangeSampleNode, TraceRawContent, + IOSample, + IOSampleNode, image_info_key, sample_info_key, ) @@ -58,6 +61,8 @@ cpu_batch_interval_sample_table_name = "CPUBatchIntervalSampleModel" mem_sample_table_name = "MemSampleModel" mem_batch_sample_table_name = "MemBatchSampleModel" +mem_alloc_sample_table_name = "MemAllocSampleModel" +mem_alloc_batch_sample_table_name = "MemAllocBatchSampleModel" date_sample_table_name = "DateSampleModel" date_batch_sample_table_name = "DateBatchSampleModel" lock_sample_table_name = "LockSampleModel" @@ -70,6 +75,8 @@ profile_batch_sample_table_name = "ProfileBatchSampleModel" time_range_sample_table_name = "TimeRangeSampleModel" time_range_batch_sample_table_name = "TimeRangeBatchSampleModel" +io_sample_table_name = "IOSampleModel" +io_batch_sample_table_name = "IOBatchSampleModel" def gen_callstack_map(callstack_table: bytes) -> Dict[int, CallstackNode]: @@ -673,6 +680,85 @@ def gen_mem_sample_list_v3(mem_sample_bytes: bytes) -> List[MemSampleNode]: return result +def gen_mem_sample_list_v4(sample_bytes: bytes) -> List[MemSampleNode]: + result: List[MemSampleNode] = [] + step_size = 4 + for idx in range(0, len(sample_bytes), 7 * step_size): + time = int.from_bytes(sample_bytes[idx : idx + step_size], byteorder="little") + cpu_time = int.from_bytes( + sample_bytes[idx + step_size : idx + 2 * step_size], + byteorder="little", + ) + alloc_size = int.from_bytes( + sample_bytes[idx + 2 * step_size : idx + 3 * step_size], + byteorder="little", + ) + alloc_count = int.from_bytes( + sample_bytes[idx + 3 * step_size : idx + 4 * step_size], + byteorder="little", + ) + stack_id = int.from_bytes( + sample_bytes[idx + 4 * step_size : idx + 5 * step_size], + byteorder="little", + ) + alloc_addr = int.from_bytes( + sample_bytes[idx + 5 * step_size : idx + 6 * step_size], + byteorder="little", + ) + curr_size = int.from_bytes( + sample_bytes[idx + 6 * step_size : idx + 7 * step_size], + byteorder="little", + ) + + time *= 10 + cpu_time *= 10 + + result.append( + MemSampleNode(alloc_addr, curr_size, time, cpu_time, stack_id, alloc_size, alloc_count) + ) + + return result + + +def gen_mem_alloc_sample_list_v4(sample_bytes: bytes) -> List[MemSampleNode]: + result: List[MemSampleNode] = [] + step_size = 4 + for idx in range(0, len(sample_bytes), 8 * step_size): + time = int.from_bytes(sample_bytes[idx : idx + step_size], byteorder="little") + cpu_time = int.from_bytes( + sample_bytes[idx + step_size : idx + 2 * step_size], + byteorder="little", + ) + alloc_size = int.from_bytes( + sample_bytes[idx + 2 * step_size : idx + 3 * step_size], + byteorder="little", + ) + alloc_count = int.from_bytes( + sample_bytes[idx + 3 * step_size : idx + 4 * step_size], + byteorder="little", + ) + stack_id = int.from_bytes( + sample_bytes[idx + 4 * step_size : idx + 5 * step_size], + byteorder="little", + ) + alloc_addr = int.from_bytes( + sample_bytes[idx + 5 * step_size : idx + 7 * step_size], + byteorder="little", + ) + curr_size = int.from_bytes( + sample_bytes[idx + 7 * step_size : idx + 8 * step_size], + byteorder="little", + ) + + time *= 10 + cpu_time *= 10 + + result.append( + MemSampleNode(alloc_addr, curr_size, time, cpu_time, stack_id, alloc_size, alloc_count) + ) + + return result + def gen_interval_sample_list(interval_sample_bytes: bytes) -> List[IntervalSampleNode]: result: List[IntervalSampleNode] = [] step_size = 4 @@ -908,6 +994,46 @@ def gen_cpu_sample_list_v3(sample_bytes: bytes) -> List[SampleNode]: return result +def gen_io_sample_list_v4(sample_bytes: bytes) -> List[IOSampleNode]: + result: List[IOSampleNode] = [] + step_size = 4 + for idx in range(0, len(sample_bytes), 7 * step_size): + time = int.from_bytes(sample_bytes[idx : idx + step_size], byteorder="little") + cpu_time = int.from_bytes( + sample_bytes[idx + step_size : idx + 2 * step_size], + byteorder="little", + ) + alloc_size = int.from_bytes( + sample_bytes[idx + 2 * step_size : idx + 3 * step_size], + byteorder="little", + ) + alloc_count = int.from_bytes( + sample_bytes[idx + 3 * step_size : idx + 4 * step_size], + byteorder="little", + ) + stack_id = int.from_bytes( + sample_bytes[idx + 4 * step_size : idx + 5 * step_size], + byteorder="little", + ) + type = int.from_bytes( + sample_bytes[idx + 5 * step_size : idx + 5 * step_size + 1], + byteorder="little", + ) + size = int.from_bytes( + sample_bytes[idx + 5 * step_size + 1: idx + 7 * step_size], + byteorder="little", + ) + + time *= 10 + cpu_time *= 10 + + result.append( + IOSampleNode(time, cpu_time, stack_id, alloc_size, alloc_count, type, size) + ) + + return result + + def table_exists(con: sqlite3.Connection, table_name: str) -> bool: table_exist_sql = f'select count(*) from sqlite_master where type="table" and name = "{table_name}";' res = con.execute(table_exist_sql) @@ -1424,6 +1550,22 @@ def gen_mem_sample_v3(row) -> MemSample: mem_sample.start_cpu_time *= 10 return mem_sample + + def gen_mem_sample_v4(row) -> MemSample: + tid = row["tid"] + start_time = int(row["time"]) * 10 + cpu_time = int(row["cpu_time"]) * 10 + alloc_size = int(row["alloc_size"]) + alloc_count = int(row["alloc_count"]) + alloc_addr = int(row["alloc_addr"]) + curr_size = int(row["curr_size"]) + stack_id = int(row["stack_id"]) + + mem_sample = MemSample( + tid, curr_size, start_time, cpu_time, stack_id, alloc_size, alloc_count + ) + + return mem_sample mem_sample_sql = ( f"select * from {mem_sample_table_name} where trace_id={trace_id};" @@ -1439,6 +1581,45 @@ def gen_mem_sample_v3(row) -> MemSample: mem_sample = gen_mem_sample_v2(one) elif version == 3: mem_sample = gen_mem_sample_v3(one) + elif version == 4: + mem_sample = gen_mem_sample_v4(one) + else: + raise RuntimeError( + "Unsupported version, please update 'sol' command line tool!" + ) + + tid = mem_sample.tid + tid_trace_list = mem_sample_map.get(tid) or [] + tid_trace_list.append(mem_sample) + mem_sample_map[tid] = tid_trace_list + + if table_exists(con, mem_alloc_sample_table_name): + def gen_mem_alloc_sample_v4(row) -> MemSample: + tid = row["tid"] + start_time = int(row["time"]) * 10 + cpu_time = int(row["cpu_time"]) * 10 + alloc_size = int(row["alloc_size"]) + alloc_count = int(row["alloc_count"]) + alloc_addr = int(row["alloc_addr"]) + # curr_alloc_size = int(row["curr_alloc_size"]) + stack_id = int(row["stack_id"]) + + mem_sample = MemSample( + tid, 0, start_time, cpu_time, stack_id, alloc_size, alloc_count + ) + + return mem_sample + + mem_sample_sql = ( + f"select * from {mem_alloc_sample_table_name} where trace_id={trace_id};" + ) + res = con.execute(mem_sample_sql) + + for one in res.fetchall(): + mem_sample: MemSample + + if version == 4: + mem_sample = gen_mem_alloc_sample_v4(one) else: raise RuntimeError( "Unsupported version, please update 'btrace' command line tool!" @@ -1467,18 +1648,61 @@ def gen_mem_sample_v3(row) -> MemSample: mem_sample_list = gen_mem_sample_list_v2(mem_sample_bytes) elif version == 3: mem_sample_list = gen_mem_sample_list_v3(mem_sample_bytes) + elif version == 4: + mem_sample_list = gen_mem_sample_list_v4(mem_sample_bytes) else: raise RuntimeError( "Unsupported version, please update 'btrace' command line tool!" ) - for mem_sample in mem_sample_list: - alloc_size = int(mem_sample.size) - start_time = mem_sample.start_time - cpu_time = mem_sample.start_cpu_time - stack_id = mem_sample.stack_id - alloc_size = mem_sample.alloc_size - alloc_count = mem_sample.alloc_count + for mem_sample_node in mem_sample_list: + curr_size = int(mem_sample_node.size) + start_time = mem_sample_node.start_time + cpu_time = mem_sample_node.start_cpu_time + stack_id = mem_sample_node.stack_id + alloc_size = mem_sample_node.alloc_size + alloc_count = mem_sample_node.alloc_count + + mem_sample = MemSample( + tid, + curr_size, + start_time, + cpu_time, + stack_id, + alloc_size, + alloc_count, + ) + + tid_trace_list = mem_sample_map.get(tid) or [] + tid_trace_list.append(mem_sample) + mem_sample_map[tid] = tid_trace_list + + if table_exists(con, mem_alloc_batch_sample_table_name): + mem_sample_sql = ( + f"select * from {mem_alloc_batch_sample_table_name} where trace_id={trace_id};" + ) + res = con.execute(mem_sample_sql) + + for one in res.fetchall(): + trace_id = one["trace_id"] + tid = one["tid"] + mem_sample_bytes: bytes = one["nodes"] + mem_sample_list: Optional[List[MemSampleNode]] = None + + if version == 4: + mem_sample_list = gen_mem_alloc_sample_list_v4(mem_sample_bytes) + else: + raise RuntimeError( + "Unsupported version, please update 'sol' command line tool!" + ) + + for mem_sample_node in mem_sample_list: + alloc_size = int(mem_sample_node.size) + start_time = mem_sample_node.start_time + cpu_time = mem_sample_node.start_cpu_time + stack_id = mem_sample_node.stack_id + alloc_size = mem_sample_node.alloc_size + alloc_count = mem_sample_node.alloc_count mem_sample = MemSample( tid, @@ -1506,6 +1730,108 @@ def gen_mem_sample_v3(row) -> MemSample: return mem_sample_map +def read_io_samples( + con: sqlite3.Connection, + trace_id: int, + callstack_table: Dict[int, CallstackNode], + version: int, +) -> Dict[int, List[IOSample]]: + io_sample_map: Dict[int, List[IOSample]] = {} + + if table_exists(con, io_sample_table_name): + def gen_io_sample_v4(row) -> IOSample: + tid = row["tid"] + start_time = int(row["time"]) * 10 + cpu_time = int(row["cpu_time"]) * 10 + alloc_size = int(row["alloc_size"]) + alloc_count = int(row["alloc_count"]) + stack_id = int(row["stack_id"]) + type_size = int(row["type_size"]) + type = (type_size & 0xff) + size = (type_size >> 8) + + io_sample = IOSample( + tid, start_time, cpu_time, stack_id, + alloc_size, alloc_count, type, size + ) + + return io_sample + + io_sample_sql = ( + f"select * from {io_sample_table_name} where trace_id={trace_id};" + ) + res = con.execute(io_sample_sql) + + for one in res.fetchall(): + io_sample: IOSample + + if version == 4: + io_sample = gen_io_sample_v4(one) + else: + raise RuntimeError( + "Unsupported version, please update 'sol' command line tool!" + ) + + tid = io_sample.tid + tid_trace_list = io_sample_map.get(tid) or [] + tid_trace_list.append(io_sample) + io_sample_map[tid] = tid_trace_list + + if table_exists(con, io_batch_sample_table_name): + io_sample_sql = ( + f"select * from {io_batch_sample_table_name} where trace_id={trace_id};" + ) + res = con.execute(io_sample_sql) + + for one in res.fetchall(): + trace_id = one["trace_id"] + tid = one["tid"] + io_sample_bytes: bytes = one["nodes"] + io_sample_list: Optional[List[IOSampleNode]] = None + + if version == 4: + io_sample_list = gen_io_sample_list_v4(io_sample_bytes) + else: + raise RuntimeError( + "Unsupported version, please update 'sol' command line tool!" + ) + + for io_sample_node in io_sample_list: + start_time = io_sample_node.start_time + cpu_time = io_sample_node.start_cpu_time + stack_id = io_sample_node.stack_id + alloc_size = io_sample_node.alloc_size + alloc_count = io_sample_node.alloc_count + type = int(io_sample_node.type) + size = int(io_sample_node.size) + + io_sample = IOSample( + tid, + start_time, + cpu_time, + stack_id, + alloc_size, + alloc_count, + type, + size + ) + + tid_trace_list = io_sample_map.get(tid) or [] + tid_trace_list.append(io_sample) + io_sample_map[tid] = tid_trace_list + + for tid, tid_trace_list in io_sample_map.items(): + for idx, io_sample in enumerate(tid_trace_list): + stack_id = io_sample.stack_id + stack = unwind_stack_from_callstack_table(callstack_table, stack_id) + + if len(stack) == 0 or stack[0] == 0: + continue + + io_sample.calls = stack + + return io_sample_map + def read_async_samples( con: sqlite3.Connection, @@ -1952,6 +2278,8 @@ def read_lock_samples( version: int, ) -> Dict[int, List[LockSample]]: lock_sample_map: Dict[int, List[LockSample]] = {} + lock_contention_map: Dict[int, List[LockContentionPair]] = {} + unlock_contention_list: List[LockSample] = [] if table_exists(con, lock_sample_table_name): # lock sample @@ -2008,17 +2336,17 @@ def read_lock_samples( "Unsupported version, please update 'btrace' command line tool!" ) - for lock_sample in sample_list: - start_time = lock_sample.start_time - cpu_time = lock_sample.start_cpu_time - stack_id = lock_sample.stack_id - alloc_size = lock_sample.alloc_size - alloc_count = lock_sample.alloc_count + for lock_sample_node in sample_list: + start_time = lock_sample_node.start_time + cpu_time = lock_sample_node.start_cpu_time + stack_id = lock_sample_node.stack_id + alloc_size = lock_sample_node.alloc_size + alloc_count = lock_sample_node.alloc_count lock_sample = LockSample( tid, - lock_sample.id, - lock_sample.action, + lock_sample_node.id, + lock_sample_node.action, start_time, cpu_time, stack_id, @@ -2028,10 +2356,41 @@ def read_lock_samples( tid_trace_list = lock_sample_map.get(tid) or [] tid_trace_list.append(lock_sample) lock_sample_map[tid] = tid_trace_list + + lock_contention_set = set([LockAction.kMtxLockContention, + LockAction.kRdlockContention, + LockAction.kWrlockContention, + LockAction.kUnfairLockContention, + LockAction.kCondWaitCtn]) + unlock_contention_set = set([LockAction.kMtxUnlockContention, + LockAction.kRwUnlockContention, + LockAction.kUnfairUnlockContention, + LockAction.kCondSignalCtn, + LockAction.kCondBroadcastCtn]) for tid, tid_trace_list in lock_sample_map.items(): + + tid_trace_list.sort(key=sample_info_key) for idx, lock_sample in enumerate(tid_trace_list): + + # TODO, handle lock_sample lost + action = LockAction(lock_sample.action) + if action in lock_contention_set: + lock_contention_pair_list: List[LockContentionPair] = lock_contention_map.get(lock_sample.id, []) + + if 0 < len(lock_contention_pair_list) and not lock_contention_pair_list[-1].end: + lock_contention_pair_list[-1].end = lock_sample + else: + lock_contention_pair = LockContentionPair(lock_sample.id) + lock_contention_pair.begin = lock_sample + lock_contention_pair_list.append(lock_contention_pair) + + lock_contention_map[lock_sample.id] = lock_contention_pair_list + + elif action in unlock_contention_set: + unlock_contention_list.append(lock_sample) + stack = [] stack_id = lock_sample.stack_id @@ -2041,6 +2400,24 @@ def read_lock_samples( continue lock_sample.calls = stack + + for unlock_contention_sample in unlock_contention_list: + lock_id = unlock_contention_sample.id + lock_contention_pair_list = lock_contention_map.get(lock_id, []) + + for lock_contention_pair in lock_contention_pair_list: + if lock_contention_pair.begin.start_time <= unlock_contention_sample.start_time and \ + unlock_contention_sample.start_time <= lock_contention_pair.end.start_time: + lock_contention_pair.begin.unlock_contention_list.append(unlock_contention_sample) + + # for lock_id, lock_contention_pair_list in lock_contention_map.items(): + # for lock_contention_pair in lock_contention_pair_list: + # # lock_contention_pair.begin.unlock_contention_list = lock_contention_pair.unlock_contention_list + # unlock_contention_list = lock_contention_pair.begin.unlock_contention_list + # if unlock_contention_list: + # print(f"lock: {lock_id}, action: {LockAction(lock_contention_pair.begin.action)}, begin time: {lock_contention_pair.begin.start_time}, end time: {lock_contention_pair.end.start_time}") + # for unlock_contention_sample in unlock_contention_list: + # print(f"\tunlock contention, tid: {unlock_contention_sample.tid}, lock: {lock_id}, action: {LockAction(unlock_contention_sample.action)}, time: {unlock_contention_sample.start_time}") return lock_sample_map @@ -2085,9 +2462,10 @@ def read_thread_info( def read_custome_perfetto_info( - con: sqlite3.Connection, trace_id: int, version: int + con: sqlite3.Connection, trace_id: int, version: int, trace_start_time: int ) -> Tuple[Dict[str, List], Dict[str, List[PerfettoCounterInfo]]]: slice_info_map: Dict[str, List] = {} + slice_info_v2_map: Dict[str, List] = {} counter_info_map: Dict[str, List[PerfettoCounterInfo]] = {} if not table_exists(con, timeseries_info_table_name): @@ -2132,6 +2510,36 @@ def read_custome_perfetto_info( slice_info_list = slice_info_map.get(type) or [] slice_info_list.append(slice_info) slice_info_map[type] = slice_info_list + elif perfetto_type == "slice_v2": + begin_time = int(info["begin_time"]) + end_time = int(info["end_time"]) + name = info.get("name", "") + debug_info = info.get("debug_info", {}) + + if end_time < begin_time: + continue + + slice_info = PerfettoSliceInfo(type, begin_time, end_time, name, debug_info) + slice_info_list = slice_info_v2_map.get(type) or [] + slice_info_list.append(slice_info) + slice_info_v2_map[type] = slice_info_list + + slice_info_map.update(slice_info_v2_map) + + def slice_info_key(slice_info: PerfettoSliceInfo): + return slice_info.begin_time + + for _, slice_info_list in slice_info_v2_map.items(): + slice_info_list.sort(key=slice_info_key) + + for item in slice_info_list: + item: PerfettoSliceInfo = item + item.begin_time -= trace_start_time + item.end_time -= trace_start_time + if item.begin_time < 0: + item.begin_time = 0 + if item.end_time < 0: + item.end_time = 0 return slice_info_map, counter_info_map @@ -2181,9 +2589,9 @@ def read_file(path: str) -> TraceRawContent: callstack_table = read_callstack_table(con, trace_id, version) thread_info_map = read_thread_info(con, trace_id, version) slice_info_map, counter_info_map = read_custome_perfetto_info( - con, trace_id, version + con, trace_id, version, trace_start_time ) - + cpu_sample_map = read_cpu_samples(con, trace_id, callstack_table, version) mem_sample_map = read_mem_samples(con, trace_id, callstack_table, version) async_sample_map = read_async_samples(con, trace_id, callstack_table, version) @@ -2192,6 +2600,7 @@ def read_file(path: str) -> TraceRawContent: lock_sample_map = read_lock_samples(con, trace_id, callstack_table, version) profile_sample_map = read_profile_samples(con, trace_id, callstack_table, version) time_range_sample_map = read_time_range_samples(con, trace_id, callstack_table, version) + io_sample_map = read_io_samples(con, trace_id, callstack_table, version) total_cpu_sample_list: List[CPUSample] = [] sample_map: Dict[int, List[CPUSample]] = {} @@ -2235,6 +2644,24 @@ def read_file(path: str) -> TraceRawContent: cpu_sample.type = "mem" sample_list.append(cpu_sample) sample_map[tid] = sample_list + + for tid, io_sample_list in io_sample_map.items(): + sample_list = sample_map.get(tid, []) + for io_sample in io_sample_list: + cpu_sample = CPUSample( + io_sample.tid, + io_sample.start_time, + io_sample.start_time, + io_sample.start_cpu_time, + io_sample.start_cpu_time, + io_sample.stack_id, + io_sample.alloc_size, + io_sample.alloc_count, + io_sample.calls, + ) + cpu_sample.type = "mem" + sample_list.append(cpu_sample) + sample_map[tid] = sample_list for tid, async_sample_list in async_sample_map.items(): sample_list = sample_map.get(tid, []) @@ -2287,6 +2714,10 @@ def read_file(path: str) -> TraceRawContent: lock_sample.calls, ) cpu_sample.type = "lock" + + if lock_sample.unlock_contention_list: + cpu_sample.unlock_contention_list = lock_sample.unlock_contention_list + sample_list.append(cpu_sample) sample_map[tid] = sample_list @@ -2343,7 +2774,7 @@ def read_file(path: str) -> TraceRawContent: val: CPUSample = thread_cpu_sample_map.values()[idx - 1] if start_time == timestamp: - diff_cpu_time = sample.start_cpu_time - val.start_cpu_time + diff_cpu_time = max(sample.start_cpu_time - val.start_cpu_time, 100) sample.start_time += diff_cpu_time sample.end_time += diff_cpu_time timestamp = sample.start_time diff --git a/btrace-iOS/BTraceTool/btrace/model.py b/btrace-iOS/BTraceTool/btrace/model.py index 349f65c..ff39b31 100644 --- a/btrace-iOS/BTraceTool/btrace/model.py +++ b/btrace-iOS/BTraceTool/btrace/model.py @@ -11,7 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict, List +from typing import Dict, List, Optional from enum import Enum @@ -72,10 +72,12 @@ def __init__(self, type: str, time: int, val: int): class PerfettoSliceInfo: - def __init__(self, type: str, begin_time: int, end_time: int): + def __init__(self, type: str, begin_time: int, end_time: int, name: str="", debug_info: Dict={}): self.type = type + self.name = name self.begin_time = begin_time self.end_time = end_time + self.debug_info = debug_info class AsyncSampleNode: @@ -193,6 +195,9 @@ class LockAction(Enum): kUnfairTrylock = 20 kUnfairLockContention = 21 kUnfairUnlockContention = 22 + kCondWaitCtn = 23 + kCondSignalCtn = 24 + kCondBroadcastCtn = 25 class LockSampleNode: @@ -239,6 +244,15 @@ def __init__( self.calls: List[int] = [] self.alloc_size = alloc_size self.alloc_count = alloc_count + self.unlock_contention_list: List[LockSample] = [] + +class LockContentionPair: + + def __init__(self, id: int): + self.id = id + self.begin: LockSample = None + self.end: LockSample = None + self.unlock_contention_list: List[LockSample] = [] class DateSample: @@ -273,6 +287,51 @@ def __init__( self.alloc_size = alloc_size self.alloc_count = alloc_count +class IOSample: + + def __init__( + self, + tid: int, + time: int, + cpu_time: int, + stack_id: int, + alloc_size: int, + alloc_count: int, + type: int, + size: int + ): + self.type = "io" + self.tid = tid + self.start_time = time + self.start_cpu_time = cpu_time + self.stack_id = stack_id + self.calls: List[int] = [] + self.alloc_size = alloc_size + self.alloc_count = alloc_count + self.type = type + self.size = size + + +class IOSampleNode: + + def __init__( + self, + time: int, + cpu_time: int, + stack_id: int, + alloc_size: int, + alloc_count: int, + type: int, + size: int + ): + self.start_time = time + self.start_cpu_time = cpu_time + self.stack_id = stack_id + self.alloc_size = alloc_size + self.alloc_count = alloc_count + self.type = type + self.size = size + class MemSample: @@ -342,6 +401,7 @@ def __init__( self.alloc_size = alloc_size self.alloc_count = alloc_count self.calls: List[int] = calls + self.unlock_contention_list: List[LockSample] = [] class IntervalSampleNode: @@ -496,6 +556,7 @@ def __init__( self.alloc_count = end_alloc_count - start_alloc_count self.children: List[MethodNode] = [] self.parent: MethodNode = None + self.unlock_contention_list: List[LockSample] = [] def update_cpu_time(self, cpu_time): if self.end_cpu_time < cpu_time: diff --git a/btrace-iOS/BTraceTool/btrace/parse.py b/btrace-iOS/BTraceTool/btrace/parse.py index 644de5a..8c654b1 100644 --- a/btrace-iOS/BTraceTool/btrace/parse.py +++ b/btrace-iOS/BTraceTool/btrace/parse.py @@ -13,6 +13,7 @@ import bisect import os +import subprocess from typing import Dict, List, Set, Optional, Tuple from btrace import symbol_info from btrace.extractor import read_file @@ -28,6 +29,7 @@ ) from btrace.open_trace import open_trace from btrace.pb import perfetto_pb2 +from btrace import util from btrace.perfetto import gen_perfetto_trace @@ -79,6 +81,37 @@ def symbolicate( image_dsym_path_map, image_info, address_list, addr_symbol_map ) + cpp_demangle_info_list: List[SymbolInfo] = [] + cpp_demangle_name_list: List[str] = [] + for _, info in addr_symbol_map.items(): + if info.name.startswith("_Z") and info.name.find("metasec") == -1 and \ + info.name.find("[") == -1: + cpp_demangle_info_list.append(info) + cpp_demangle_name_list.append(f"_{info.name}") + + if len(cpp_demangle_name_list): + batch = 0 + batch_size = 100 + while True: + begin_idx = batch * batch_size + + if len(cpp_demangle_name_list) <= begin_idx: + break + + end_idx = min((batch+1) * batch_size, len(cpp_demangle_name_list)) + cpp_name_list = " ".join(cpp_demangle_name_list[begin_idx:end_idx]) + cmd_str = f"c++filt {cpp_name_list}" + demangle_name_list: List[str] = subprocess.run(cmd_str, shell=True, capture_output=True).stdout.decode().strip().split("\n") + + if len(demangle_name_list) == end_idx - begin_idx: + for i in range(begin_idx, end_idx): + info = cpp_demangle_info_list[i] + demangle_name = demangle_name_list[i-begin_idx] + if demangle_name: + info.name = demangle_name + + batch += 1 + return addr_symbol_map @@ -97,7 +130,7 @@ def beautify_method_tree(root: MethodNode): next_time = next_node.start_time diff_time = next_time - node.end_time - node.end_time = node.end_time + int(min(diff_time, 1000)) # 1000us + node.end_time = node.end_time + int(min(diff_time, 100)) # 1000us child_wall_time = node.end_time - node.start_time if child_wall_time <= 0: @@ -134,7 +167,8 @@ def construct_method_tree( alloc_size = sample.alloc_size alloc_count = sample.alloc_count stack = sample.calls - + unlock_contention_list = sample.unlock_contention_list + if idx == 0: parent_node = method_tree.root @@ -151,6 +185,10 @@ def construct_method_tree( alloc_count, alloc_count, ) + + if unlock_contention_list and i == len(stack)-1: + node.unlock_contention_list = unlock_contention_list + method_tree.put_child(node, parent_node) parent_node = node else: @@ -177,6 +215,9 @@ def construct_method_tree( last_child.update_end_time(end_time) last_child.update_alloc_size(alloc_size) last_child.update_alloc_count(alloc_count) + + if unlock_contention_list and j == len(stack)-1: + last_child.unlock_contention_list = unlock_contention_list parent_node = last_child i += 1 @@ -196,6 +237,10 @@ def construct_method_tree( prev_sample.alloc_count, alloc_count, ) + + if unlock_contention_list and j == len(stack)-1: + node.unlock_contention_list = unlock_contention_list + method_tree.put_child(node, parent_node) parent_node = node j += 1 @@ -222,11 +267,11 @@ def parse_raw_data( cpu_sample_list = raw_content.cpu_sample_list image_info_list = raw_content.image_info_list - image_info_map: Dict[ImageInfo] = {} + image_info_map: Dict[str, ImageInfo] = {} image_addr_set_map: Dict[str, Set[int]] = {} image_address_list = [image_info.address for image_info in image_info_list] - for sample in cpu_sample_list: + for sample in cpu_sample_list: for address in sample.calls: image_info_idx = get_image_info(image_address_list, address) @@ -270,7 +315,7 @@ def parse_raw_data( for address in sample.calls: symbol_info = addr_symbol_map.get(address) - + if symbol_info: stack.append(address) @@ -303,12 +348,13 @@ def parse_raw_data( def parse( - file_path: str, dsym_path: str, force: bool = False, sys_symbol: bool = False + file_path: str, dsym_path: str, force: bool = False, sys_symbol: bool = False, + upload: bool = False ): print("parsing file, please wait...") if not os.path.exists(dsym_path): - err_msg = "Dsym path does not exist!" + err_msg = f"Dsym path {dsym_path} does not exist!" raise RuntimeError(err_msg) file_prefix, file_ext = os.path.splitext(file_path) @@ -338,4 +384,4 @@ def parse( trace_bytes = trace_pb.SerializeToString() fp.write(trace_bytes) - open_trace(output_path) + open_trace(output_path) \ No newline at end of file diff --git a/btrace-iOS/BTraceTool/btrace/perfetto.py b/btrace-iOS/BTraceTool/btrace/perfetto.py index 6930117..3887760 100644 --- a/btrace-iOS/BTraceTool/btrace/perfetto.py +++ b/btrace-iOS/BTraceTool/btrace/perfetto.py @@ -152,6 +152,13 @@ def method_tree_to_trace(trace_packet_list: List[perfetto_pb2.TracePacket], thre "alloc_size": alloc_size, "alloc_count": alloc_count } + + if method_node.unlock_contention_list: + contention_tids = [str(x.tid) for x in method_node.unlock_contention_list] + debug_infos["locked_by"] = ", ".join(contention_tids) + debug_infos["lock_id"] = str(method_node.unlock_contention_list[0].id) + symbol_name = symbol_name + "(lock_contention)" + start_packet = gen_slice_event_packet(thread_track.uuid, start_time, symbol_name, perfetto_pb2.TrackEvent.Type.TYPE_SLICE_BEGIN, debug_infos) trace_packet_list.append(start_packet) @@ -195,15 +202,22 @@ def gen_perfetto_trace(method_tree_list: List[MethodTree], counter_event = gen_counter_event_packet(counter_track.uuid, time, val) trace_packet_list.append(counter_event) - for slice_name, v in slice_info_map.items(): + for slice_type, v in slice_info_map.items(): slice_info_list: List[PerfettoSliceInfo] = v - slice_packet = gen_sub_packet(auto_increase.get(), root_uuid, f"{slice_name}") + slice_packet = gen_sub_packet(auto_increase.get(), root_uuid, f"{slice_type}") trace_packet_list.append(slice_packet) for slice_info in slice_info_list: + slice_name: str = "" + if len(slice_info.name): + slice_name = slice_info.name + else: + slice_name = slice_type + + debug_info = slice_info.debug_info slice_track = slice_packet.track_descriptor begin_time = slice_info.begin_time * 1000 - start_packet = gen_slice_event_packet(slice_track.uuid, begin_time, slice_name, perfetto_pb2.TrackEvent.Type.TYPE_SLICE_BEGIN) + start_packet = gen_slice_event_packet(slice_track.uuid, begin_time, slice_name, perfetto_pb2.TrackEvent.Type.TYPE_SLICE_BEGIN, debug_info) trace_packet_list.append(start_packet) end_time = slice_info.end_time * 1000 end_packet = gen_slice_event_packet(slice_track.uuid, end_time, slice_name, perfetto_pb2.TrackEvent.Type.TYPE_SLICE_END) diff --git a/btrace-iOS/BTraceTool/btrace/util.py b/btrace-iOS/BTraceTool/btrace/util.py index 33b6456..67ad36f 100644 --- a/btrace-iOS/BTraceTool/btrace/util.py +++ b/btrace-iOS/BTraceTool/btrace/util.py @@ -11,45 +11,86 @@ # See the License for the specific language governing permissions and # limitations under the License. +import gzip +import json import os import sys import signal import subprocess from typing import Dict, List, Optional, Union +import requests + SRC_PORT = 11288 DST_PORT = 1128 API_HOST = "http://localhost:" + str(SRC_PORT) BTRACE_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -TIMESERIES_CONFIG = {} +TIMESERIES_CONFIG = {"mem_usage": True, "cpu_usage": True} TIMEPROFILER_CONFIG = {"period": 1, "bg_period": 2, "active_only": False, "merge_stack": False} MEMPROFILER_CONFIG = {"lite_mode": 0, "interval": 10, "period": 1, "bg_period": 2} DATEPROFILER_CONFIG = {"period": 1, "bg_period": 2} DISPATCHPROFILER_CONFIG = {"period": 1, "bg_period": 2} LOCKPROFILER_CONFIG = {"period": 1, "bg_period": 2} +IOPROFILER_CONFIG = {"period": 1, "bg_period": 2} DEFAULT_CONFIG = { "enable": True, "timeout": 36000, "max_records": 10000, + "dump_queue_name": True, "plugins": [ {"name": "timeseries", "config": TIMESERIES_CONFIG}, {"name": "timeprofiler", "config": TIMEPROFILER_CONFIG}, {"name": "memoryprofiler", "config": MEMPROFILER_CONFIG}, {"name": "dateprofiler", "config": DATEPROFILER_CONFIG}, - {"name": "dispatchprofiler", "config": DISPATCHPROFILER_CONFIG}, - {"name": "lockprofiler", "config": LOCKPROFILER_CONFIG} + # {"name": "dispatchprofiler", "config": DISPATCHPROFILER_CONFIG}, + {"name": "lockprofiler", "config": LOCKPROFILER_CONFIG}, + # {"name": "ioprofiler", "config": IOPROFILER_CONFIG} ] } VERBOSE = False -def set_record_parameter(main_only=False, hitch_thres=50): +def set_record_parameter(main_only=False, hitch_thres=50, frequency: str=""): plugins: List[Dict] = DEFAULT_CONFIG["plugins"] - + + high_freq = False + frequency_num = 1 + + if frequency: + try: + frequency = frequency.strip() + frequency_num = int(frequency) + except: + if frequency.endswith("ms"): + frequency_num = int(frequency[:-2]) + frequency_num = max(frequency_num, 1) + elif frequency.endswith("us"): + high_freq = True + frequency_num = int(frequency[:-2]) + frequency_num = max(frequency_num, 100) + else: + raise RuntimeError("Only accept 'ms' or 'us'") + DEFAULT_CONFIG["main_only"] = main_only + DEFAULT_CONFIG["high_freq"] = high_freq + + if high_freq: + MEMPROFILER_CONFIG["interval"] = 3 + + if 1 != frequency_num: + TIMEPROFILER_CONFIG["period"] = frequency_num + TIMEPROFILER_CONFIG["bg_period"] = 2 * frequency_num + MEMPROFILER_CONFIG["period"] = frequency_num + MEMPROFILER_CONFIG["bg_period"] = 2 * frequency_num + DATEPROFILER_CONFIG["period"] = frequency_num + DATEPROFILER_CONFIG["bg_period"] = 2 * frequency_num + DISPATCHPROFILER_CONFIG["period"] = frequency_num + DISPATCHPROFILER_CONFIG["bg_period"] = 2 * frequency_num + LOCKPROFILER_CONFIG["period"] = frequency_num + LOCKPROFILER_CONFIG["bg_period"] = 2 * frequency_num if main_only: TIMEPROFILER_CONFIG["bg_period"] = 10_000_000 @@ -58,7 +99,7 @@ def set_record_parameter(main_only=False, hitch_thres=50): DISPATCHPROFILER_CONFIG["bg_period"] = 10_000_000 LOCKPROFILER_CONFIG["bg_period"] = 10_000_000 - MONITOR_CONFIG = {"hang": {"config": {"hitch": hitch_thres}}} + MONITOR_CONFIG = {"hang": {"config": {"hitch": hitch_thres, "perfetto": True}}} MONITOR_PLUGIN = {"name": "monitor", "config": MONITOR_CONFIG} plugins.append(MONITOR_PLUGIN) @@ -116,39 +157,43 @@ def clean_subprocess(pid_list: List[int]): kill_process(pid) -def macho_uuid(file_path) -> Optional[str]: +def macho_uuid(file_path: str) -> Optional[str]: uuid = "" from macholib import MachO, mach_o import lief + if not lief.is_macho(file_path): return uuid - - # fat_bin = lief.MachO.parse(file_path) - - # if not fat_bin: - # return uuid - # arm64_bin: Optional[lief.MachO.Binary] = None + try: + for cmd in MachO.MachO(file_path).headers[0].commands: + if cmd[0].cmd == mach_o.LC_UUID: + uudi_bytes = cmd[1].uuid + uuid = "".join([f'{i:02x}' for i in uudi_bytes]) + break + except Exception as e: + config = lief.MachO.ParserConfig().quick + fat_bin = lief.MachO.parse(file_path, config) - # for i in range(fat_bin.size): - # macho_bin = fat_bin.at(i) - # if macho_bin.header.cpu_type == lief.MachO.Header.CPU_TYPE.ARM64: - # arm64_bin = macho_bin - # break + if not fat_bin: + return uuid - # if not arm64_bin: - # return uuid + arm64_bin: Optional[lief.MachO.Binary] = None - # if arm64_bin.has_uuid: - # uuid_list: List[int] = arm64_bin.uuid.uuid - # uuid = "".join([f'{i:x}' for i in uuid_list]) - - for cmd in MachO.MachO(file_path).headers[0].commands: - if cmd[0].cmd == mach_o.LC_UUID: - uudi_bytes = cmd[1].uuid - uuid = "".join([f'{i:02x}' for i in uudi_bytes]) - break + for i in range(fat_bin.size): + macho_bin = fat_bin.at(i) + if macho_bin.header.cpu_type == lief.MachO.Header.CPU_TYPE.ARM64: + arm64_bin = macho_bin + break + + if not arm64_bin: + return uuid + + if arm64_bin.has_uuid: + uuid_list: List[int] = arm64_bin.uuid.uuid + uuid = "".join([f'{i:02x}' for i in uuid_list]) + return uuid @@ -174,9 +219,3 @@ def verbose() -> bool: def curr_interpreter() -> str: return sys.executable - - -if __name__ == "__main__": - prt(DEFAULT_CONFIG, ANSI.BG_BLUE) - set_record_parameter() - prt(DEFAULT_CONFIG, ANSI.RED) diff --git a/btrace-iOS/BTraceTool/pyproject.toml b/btrace-iOS/BTraceTool/pyproject.toml index 793f8b3..d79dd26 100644 --- a/btrace-iOS/BTraceTool/pyproject.toml +++ b/btrace-iOS/BTraceTool/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "btrace" -version = "0.8.1" +version = "0.9.2" description = "" authors = ["bytedance"] readme = "README.md" From 332c7cac450ed420fe8877682c368119b6b4a119 Mon Sep 17 00:00:00 2001 From: "yucong.park" Date: Thu, 15 Jan 2026 19:50:49 +0800 Subject: [PATCH 2/6] replace npth_dl with shadowhook internal xdl --- btrace-android/app/build.gradle | 1 - .../sample/android/ExampleInstrumentedTest.kt | 50 - .../rhea/sample/android/ExampleUnitTest.kt | 31 - .../rhea-inhouse/src/main/cpp/CMakeLists.txt | 1 - .../src/main/cpp/sampling/Stack.cpp | 4 +- .../src/main/cpp/sampling/StackVisitor.cpp | 20 +- .../cpp/trace/java_alloc/TraceJavaAlloc.cpp | 13 +- .../main/cpp/trace/java_alloc/checkpoint.cpp | 5 +- .../rhea-inhouse/src/main/cpp/utils/npth_dl.c | 976 ------------------ .../rhea-inhouse/src/main/cpp/utils/npth_dl.h | 42 - .../src/main/cpp/utils/scoped_dlopen.h | 7 +- 11 files changed, 22 insertions(+), 1128 deletions(-) delete mode 100644 btrace-android/app/src/androidTest/java/rhea/sample/android/ExampleInstrumentedTest.kt delete mode 100644 btrace-android/app/src/test/java/rhea/sample/android/ExampleUnitTest.kt delete mode 100644 btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/npth_dl.c delete mode 100644 btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/npth_dl.h diff --git a/btrace-android/app/build.gradle b/btrace-android/app/build.gradle index 659599b..a0eb7e1 100644 --- a/btrace-android/app/build.gradle +++ b/btrace-android/app/build.gradle @@ -11,7 +11,6 @@ android { targetSdkVersion versions.targetSdk versionCode 1 versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" ndk { abiFilters "arm64-v8a", "armeabi-v7a" diff --git a/btrace-android/app/src/androidTest/java/rhea/sample/android/ExampleInstrumentedTest.kt b/btrace-android/app/src/androidTest/java/rhea/sample/android/ExampleInstrumentedTest.kt deleted file mode 100644 index 7302f04..0000000 --- a/btrace-android/app/src/androidTest/java/rhea/sample/android/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,50 +0,0 @@ -package rhea.sample.android - -import android.util.Log -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.bytedance.rheatrace.core.TraceStub - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - - companion object { - const val TAG: String = "Rhea:AndroidTest" - } - - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("rhea.sample.android", appContext.packageName) - } - - @Test - fun testTraceStub() { - val testCounts = 10 - val loopCount = 1000000 - - val ids: Array = arrayOfNulls(loopCount) - for (i in 0 until loopCount) { - ids[i] = i.toString() - } - for (time in 0 until testCounts) { - val start = System.currentTimeMillis() - for (id in ids) { - TraceStub.i(id) - TraceStub.o(id) - } - Log.d(TAG, "unit test cost: " + (System.currentTimeMillis() - start) + "ms") - } - } -} \ No newline at end of file diff --git a/btrace-android/app/src/test/java/rhea/sample/android/ExampleUnitTest.kt b/btrace-android/app/src/test/java/rhea/sample/android/ExampleUnitTest.kt deleted file mode 100644 index 477a1b8..0000000 --- a/btrace-android/app/src/test/java/rhea/sample/android/ExampleUnitTest.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2021 ByteDance Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rhea.sample.android - -import org.junit.Assert.assertEquals -import org.junit.Test - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/CMakeLists.txt b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/CMakeLists.txt index 9094b48..ae152b2 100644 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/CMakeLists.txt +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/CMakeLists.txt @@ -19,7 +19,6 @@ set(RHEA_SRCS trace/java_alloc/thread_list.cpp trace/java_alloc/TraceJavaAlloc.cpp utils/JNIHook.cpp - utils/npth_dl.c TraceGlobalJni.cpp TraceAbilityJni.cpp RheaOnLoad.cpp diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/sampling/Stack.cpp b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/sampling/Stack.cpp index 39f3ada..0e5d797 100644 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/sampling/Stack.cpp +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/sampling/Stack.cpp @@ -16,7 +16,7 @@ #include "Stack.h" #include "../base/common_write.h" -#include "../utils/npth_dl.h" +#include #include #include @@ -35,7 +35,7 @@ bool Stack::init(void* libartHandle) { } if (libartHandle != nullptr) { - sPrettyMethodCall = reinterpret_cast(npth_dlsym(libartHandle, ART_METHOD_PRETTY_METHOD)); + sPrettyMethodCall = reinterpret_cast(shadowhook_dlsym(libartHandle, ART_METHOD_PRETTY_METHOD)); sInited = sPrettyMethodCall != nullptr; } diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/sampling/StackVisitor.cpp b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/sampling/StackVisitor.cpp index 687cead..ee78ee1 100644 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/sampling/StackVisitor.cpp +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/sampling/StackVisitor.cpp @@ -14,11 +14,9 @@ * limitations under the License. */ #include "StackVisitor.h" -#include "../utils/npth_dl.h" - +#include #include - namespace rheatrace { static constexpr const char* THREAD_CURRENT_FROM_GDB = "_ZN3art6Thread14CurrentFromGdbEv"; @@ -54,22 +52,22 @@ bool StackVisitor::init() { if (sInited) { return true; } - auto *handle = npth_dlopen("libart.so"); + auto *handle = shadowhook_dlopen("libart.so"); if (handle == nullptr) { return false; } - sCurrentThreadCall = reinterpret_cast(npth_dlsym(handle, THREAD_CURRENT_FROM_GDB)); - sConstructCall = reinterpret_cast(npth_dlsym(handle, STACK_VISITOR_CTOR)); - sDestructCall = reinterpret_cast(npth_dlsym(handle, STACK_VISITOR_DTOR)); - sCreateContextCall = reinterpret_cast(npth_dlsym(handle, CONTEXT_CREATE)); - sGetMethodCall = reinterpret_cast(npth_dlsym(handle, STACK_VISITOR_GET_METHOD)); - sWalkStackCall = reinterpret_cast(npth_dlsym(handle, STACK_VISITOR_WALK_STACK)); + sCurrentThreadCall = reinterpret_cast(shadowhook_dlsym(handle, THREAD_CURRENT_FROM_GDB)); + sConstructCall = reinterpret_cast(shadowhook_dlsym(handle, STACK_VISITOR_CTOR)); + sDestructCall = reinterpret_cast(shadowhook_dlsym(handle, STACK_VISITOR_DTOR)); + sCreateContextCall = reinterpret_cast(shadowhook_dlsym(handle, CONTEXT_CREATE)); + sGetMethodCall = reinterpret_cast(shadowhook_dlsym(handle, STACK_VISITOR_GET_METHOD)); + sWalkStackCall = reinterpret_cast(shadowhook_dlsym(handle, STACK_VISITOR_WALK_STACK)); if (sConstructCall != nullptr && sCreateContextCall != nullptr && sGetMethodCall != nullptr && sWalkStackCall != nullptr) { sInited = Stack::init(handle); } - npth_dlclose(handle); + shadowhook_dlclose(handle); return sInited; } diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/java_alloc/TraceJavaAlloc.cpp b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/java_alloc/TraceJavaAlloc.cpp index b773adf..4f07a41 100644 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/java_alloc/TraceJavaAlloc.cpp +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/java_alloc/TraceJavaAlloc.cpp @@ -20,7 +20,6 @@ #include "TraceJavaAlloc.h" #include -#include "../../utils/npth_dl.h" #include #include #include "java_alloc_common.h" @@ -254,7 +253,7 @@ void registerAllocationListener() { } bool TraceJavaAlloc::init() { - std::shared_ptr scope = std::shared_ptr(npth_dlopen("libart.so"), npth_dlclose); + std::shared_ptr scope = std::shared_ptr(shadowhook_dlopen("libart.so"), shadowhook_dlclose); if (scope.get() == nullptr) { ALOGE("Cannot open libart.so"); return false; @@ -265,13 +264,13 @@ bool TraceJavaAlloc::init() { return false; } - SetAllocationListenerFunc = (SetPtr_) npth_dlsym(art_lib, + SetAllocationListenerFunc = (SetPtr_) shadowhook_dlsym(art_lib, HEAP_SET_ALLOC_LISTENER); if(SetAllocationListenerFunc == nullptr) { ALOGE("Cannot find SetAllocationListener"); return false; } - RemoveAllocationListenerFunc = (CallVoid_) npth_dlsym(art_lib, + RemoveAllocationListenerFunc = (CallVoid_) shadowhook_dlsym(art_lib, HEAP_REMOVE_ALLOC_LISTENER); if(RemoveAllocationListenerFunc == nullptr) { ALOGE("Cannot find RemoveAllocationListener"); @@ -279,11 +278,11 @@ bool TraceJavaAlloc::init() { } s_use_thread_ResetQuickAllocEntryPointsForThread_bool = false; - Thread_ResetQuickAllocEntryPointsForThreadFunc = npth_dlsym(art_lib, + Thread_ResetQuickAllocEntryPointsForThreadFunc = shadowhook_dlsym(art_lib, Thread_RESET_QUICK_ALLOC_ENTRY_POINTS_FOR_THREAD); if(Thread_ResetQuickAllocEntryPointsForThreadFunc == nullptr) { s_use_thread_ResetQuickAllocEntryPointsForThread_bool = true; - Thread_ResetQuickAllocEntryPointsForThreadFunc = npth_dlsym(art_lib, + Thread_ResetQuickAllocEntryPointsForThreadFunc = shadowhook_dlsym(art_lib, Thread_RESET_QUICK_ALLOC_ENTRY_POINTS_FOR_THREAD_BOOL); } if(Thread_ResetQuickAllocEntryPointsForThreadFunc == nullptr) { @@ -291,7 +290,7 @@ bool TraceJavaAlloc::init() { return false; } - SetQuickAllocEntryPointsInstrumentedFunc = (SetQuickAllocEntryPointsInstrumented_) npth_dlsym( + SetQuickAllocEntryPointsInstrumentedFunc = (SetQuickAllocEntryPointsInstrumented_) shadowhook_dlsym( art_lib, SET_QUICK_ALLOC_ENTRY_POINTS_INSTRUMENTED); if(SetQuickAllocEntryPointsInstrumentedFunc == nullptr) { ALOGE("Cannot find SetQuickAllocEntryPointsInstrumented"); diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/java_alloc/checkpoint.cpp b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/java_alloc/checkpoint.cpp index 008fdda..662da7a 100644 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/java_alloc/checkpoint.cpp +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/java_alloc/checkpoint.cpp @@ -20,7 +20,6 @@ #include "checkpoint.h" #include #include -#include "../../utils/npth_dl.h" #include "java_alloc_common.h" #include "../../RheaContext.h" #include "../../utils/log.h" @@ -53,10 +52,10 @@ size_t run_checkpoint(void *closure) { bool init_checkpoint(void* lib) { - ThreadList_RunCheckpointFunc = (ThreadList_RunCheckpoint_) npth_dlsym(lib, + ThreadList_RunCheckpointFunc = (ThreadList_RunCheckpoint_) shadowhook_dlsym(lib, ThreadList_RUN_CHECKPOINT); if(ThreadList_RunCheckpointFunc == nullptr) { - ThreadList_RunCheckpointFunc_15 = (ThreadList_RunCheckpoint_15_) npth_dlsym(lib, + ThreadList_RunCheckpointFunc_15 = (ThreadList_RunCheckpoint_15_) shadowhook_dlsym(lib, ThreadList_RUN_CHECKPOINT_15); } if (ThreadList_RunCheckpointFunc == nullptr && ThreadList_RunCheckpointFunc_15 == nullptr) { diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/npth_dl.c b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/npth_dl.c deleted file mode 100644 index 6571ce7..0000000 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/npth_dl.c +++ /dev/null @@ -1,976 +0,0 @@ -/* - * Copyright (C) 2021 ByteDance Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// -// Created by puyingmin on 2019/12/18. -// -#define LOG_TAG "NPTH_DL" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "npth_dl.h" - -struct elf_hash_struct -{ - size_t nbucket; - size_t nchain; - unsigned *bucket; - unsigned *chain; -}; - -struct gnu_hash_struct -{ - size_t nbucket; - uint32_t *bucket; - uint32_t *chain; - uint32_t maskwords; - uint32_t shift2; - ElfW(Addr) *bloomfilter; -}; - -#define ELF_HASH_MASK (uint32_t)0x1 -#define GNU_HASH_MASK (uint32_t)0x2 -#define HAS_ELF_HASH(_flag) (((_flag) & ELF_HASH_MASK) == ELF_HASH_MASK) -#define HAS_GNU_HASH(_flag) (((_flag) & GNU_HASH_MASK) == GNU_HASH_MASK) -#define SET_ELF_HASH(_flag) do { (_flag) |= ELF_HASH_MASK; } while(0) -#define SET_GNU_HASH(_flag) do { (_flag) |= GNU_HASH_MASK; } while(0) - -typedef struct nsoinfo -{ - ElfW(Addr) base; - ElfW(Addr) load_bias; - char* path; - size_t size; - uint32_t phnum; - uint32_t hash_flag; - const ElfW(Phdr) *phdr; - const ElfW(Dyn) *dynamic; - const ElfW(Sym) *dynsym; - const char *dynstr; - - const ElfW(Sym) *symtab; - const char *strtab; - size_t nsymtab; - size_t nstrtab; - struct gnu_hash_struct gnu_hash; - struct elf_hash_struct elf_hash; - void *mapdata; - size_t mapsize; -} nsoinfo_t; - -typedef int (*DL_ITERATE_PHDR_CB)(struct dl_phdr_info* , size_t, void*); -typedef int (*DL_ITERATE_PHDR)(DL_ITERATE_PHDR_CB, void*); - -typedef struct -{ - const char* name; /* in */ - union - { - int need_path; /* in */ - void* padding1; - }; - char* path; /* out */ - ElfW(Addr) base; /* out */ - ElfW(Addr) load_bias; /* out */ - const ElfW(Phdr)* phdr; /* out */ - union - { - uint32_t phnum; /* out */ - void * padding2; - }; -} dl_search_t; - -char* get_path_from_maps(const void* address) -{ - char line[1024]; - uintptr_t start; - - FILE *f = fopen("/proc/self/maps", "r"); - if (f == NULL) return NULL; - - char * ret = NULL; - while (fgets(line, sizeof(line), f)) - { - if (1 == sscanf(line, "%"SCNxPTR"", &start) && (void *)start == address) - { - char* path = strchr(line, '/'); - if (NULL == path) break; - - char* last = strchr(path, ' '); - if (NULL == last) - { - last = strchr(path, '\n'); - if (NULL == last) break; - } - - *last = '\0'; - - ret = strdup(path); - break; - } - } - - fclose(f); - return ret; -} - -static ElfW(Addr) get_so_base(struct dl_phdr_info* info) -{ - const ElfW(Phdr) *phdr = info->dlpi_phdr; - const ElfW(Phdr) *phdr_end = phdr + info->dlpi_phnum; - - ElfW(Addr) min_vaddr = INTPTR_MAX; - - for (phdr = info->dlpi_phdr; phdr < phdr_end; phdr++) - { - if (phdr->p_type != PT_LOAD) continue; - - if (min_vaddr <= phdr->p_vaddr) continue; - - min_vaddr = phdr->p_vaddr; - } - - return min_vaddr == INTPTR_MAX ? 0 : info->dlpi_addr + min_vaddr; -} - -static int dl_iterate_phdr_cb(struct dl_phdr_info* info, size_t size, void* data) -{ - dl_search_t* search = (dl_search_t *)data; - - if (info->dlpi_name == NULL) return 0; - - const char* cmp1 = search->name; - const char* cmp2 = info->dlpi_name; - - if ('/' == cmp1[0] && '/' != cmp2[0]) - { - cmp1 = strrchr(cmp1, '/') + 1; - } - else if ('/' != cmp1[0] && '/' == cmp2[0]) - { - cmp2 = strrchr(cmp2, '/') + 1; - } - - if (0 != strcmp(cmp1, cmp2)) return 0; - - search->base = get_so_base(info); - search->load_bias = info->dlpi_addr; - search->phdr = info->dlpi_phdr; - search->phnum = (uint32_t)(info->dlpi_phnum); - - if ('/' == info->dlpi_name[0]) - { - if (search->need_path) - { - search->path = strdup(info->dlpi_name); - } - } - else if ('/' == search->name[0] || search->need_path) - { - char* path = get_path_from_maps((const void *)search->base); - - if (NULL == path) return 0; - - if ('/' == search->name[0]) - { - if (0 != strcmp(search->name, path)) - { - free(path); - return 0; - } - } - - if (search->need_path) - { - search->path = path; - } - else - { - free(path); - } - } - - return 1; -} - -static uint32_t elf_hash(const char *name) -{ - const uint8_t *ptr = (const uint8_t *) name; - uint32_t h = 0; - uint32_t g; - - while (*ptr) - { - h = (h << 4) + *ptr++; - g = h & 0xf0000000; - h ^= g; - h ^= g >> 24; - } - - return h; -} - -static uint32_t gnu_hash(const char *name) -{ - uint32_t h = 5381; - const uint8_t* ptr = (const uint8_t*)(name); - - while (*ptr != 0) - { - h += (h << 5) + *ptr++; // h*33 + c = h + h * 32 + c = h + h << 5 + c - } - - return h; -} - -static uint32_t get_sym_index_with_elf_hash(nsoinfo_t* si, const char* symname) -{ - struct elf_hash_struct* hash_struct = &si->elf_hash; - - uint32_t hash = elf_hash(symname); - uint32_t idx = hash_struct->bucket[hash % hash_struct->nbucket]; - - for ( ; idx != 0; idx = hash_struct->chain[idx]) - { - const ElfW(Sym)* s = si->dynsym + idx; - - if (strcmp(si->dynstr + s->st_name, symname) == 0) { - break; - } - } - return idx; -} - -static uint32_t get_sym_index_with_gnu_hash(nsoinfo_t* si, const char* symname) -{ - struct gnu_hash_struct* hash_struct = &si->gnu_hash; - - uint32_t hash = gnu_hash(symname); - uint32_t h2 = hash >> hash_struct->shift2; - uint32_t bloom_mask = sizeof(ElfW(Addr))*8; - uint32_t word_num = (hash / bloom_mask) & hash_struct->maskwords; - ElfW(Addr) bloom_word = hash_struct->bloomfilter[word_num]; - - uint32_t n; - uint32_t idx = 0; - - if (((0x1u & (bloom_word >> (hash % bloom_mask)) & (bloom_word >> (h2 % bloom_mask))) != 0) - && ((n = hash_struct->bucket[hash % hash_struct->nbucket])!= 0)) - { - do - { - const ElfW(Sym) *s = si->dynsym + n; - if (((hash_struct->chain[n] ^ hash) >> 0x1u) == 0 && - strcmp(si->dynstr + s->st_name, symname) == 0) - { - idx = n; - break; - } - } while ((hash_struct->chain[n++] & 0x1u) == 0); - } - return idx; -} - -static uint32_t get_sym_index(nsoinfo_t* si, const char* symname) -{ - uint32_t index = 0; - - if (HAS_GNU_HASH(si->hash_flag)) - { - index = get_sym_index_with_gnu_hash(si, symname); - } - - if (index == 0 && HAS_ELF_HASH(si->hash_flag)) - { - index = get_sym_index_with_elf_hash(si, symname); - } - - return index; -} - -static void fill_elf_hash_struct(nsoinfo_t* si, ElfW(Addr) header) -{ - struct elf_hash_struct* hash_struct = &si->elf_hash; - hash_struct->nbucket = ((uint32_t*)header)[0]; - hash_struct->nchain = ((uint32_t*)header)[1]; - hash_struct->bucket = (uint32_t*)(header + 8); - hash_struct->chain = (uint32_t*)(header + 8 + hash_struct->nbucket * 4); - - SET_ELF_HASH(si->hash_flag); -} - -static void fill_gnu_hash_struct(nsoinfo_t* si, ElfW(Addr) header) -{ - struct gnu_hash_struct* hash_struct = &si->gnu_hash; - - hash_struct->nbucket = ((uint32_t*)header)[0]; - // skip symndx - hash_struct->maskwords = ((uint32_t*)header)[2]; - hash_struct->shift2 = ((uint32_t*)header)[3]; - - hash_struct->bloomfilter = (ElfW(Addr)*)(header + 16); - hash_struct->bucket = (uint32_t*)(hash_struct->bloomfilter + hash_struct->maskwords); - // amend chain for symndx = header[1] - hash_struct->chain = hash_struct->bucket + hash_struct->nbucket - ((uint32_t*)header)[1]; - - if (!powerof2(hash_struct->maskwords)) - { - return; - } - hash_struct->maskwords--; - - SET_GNU_HASH(si->hash_flag); -} - -static nsoinfo_t* alloc_soinfo() -{ - return (nsoinfo_t *)calloc(1, sizeof(nsoinfo_t)); -} - -static void free_soinfo(nsoinfo_t* si) -{ - if (si != NULL) - { - if (si->path != NULL) - { - free(si->path); - } - if (si->mapdata != 0) - { - munmap(si->mapdata, si->mapsize); - } - free(si); - } -} - -static nsoinfo_t* parse_soinfo(dl_search_t* search) -{ - nsoinfo_t* si = alloc_soinfo(); - if (si == NULL) return NULL; - - si->base = search->base; - si->phdr = search->phdr; - si->phnum = search->phnum; - si->path = search->path; - si->load_bias = search->load_bias; - si->hash_flag = 0; - - ElfW(Addr) dynamic = 0; - for(const ElfW(Phdr) *ph = si->phdr; ph < si->phdr + si->phnum; ph++) - { - if (ph->p_type == PT_DYNAMIC) - { - dynamic = ph->p_vaddr; - } - } - - if (dynamic == 0) goto err; - - si->dynamic = (ElfW(Dyn) *)(dynamic + si->load_bias); - - for (const ElfW(Dyn)* d = si->dynamic; d->d_tag != DT_NULL; ++d) - { - switch (d->d_tag) - { - case DT_HASH: - fill_elf_hash_struct(si, si->load_bias + d->d_un.d_ptr); - break; - - case DT_GNU_HASH: - fill_gnu_hash_struct(si, si->load_bias + d->d_un.d_ptr); - break; - - case DT_STRTAB: - si->dynstr = (const char *)(si->load_bias + d->d_un.d_ptr); - break; - - case DT_SYMTAB: - si->dynsym = (ElfW(Sym) *)(si->load_bias + d->d_un.d_ptr); - break; - - default: - break; - } - } - - if (si->dynsym != NULL && si->dynstr != NULL && si->hash_flag != 0) - { - return si; - } - - err: - free_soinfo(si); - - return NULL; -} - -static void* get_so_info(const char* so_name, int need_path) -{ - DL_ITERATE_PHDR dl_iterater = (DL_ITERATE_PHDR)npth_dliterater(); - - if (dl_iterater == NULL) - { - return NULL; - } - - dl_search_t search; - - memset(&search, 0 , sizeof(dl_search_t)); - - search.name = so_name; - search.need_path = need_path; - - if (1 != dl_iterater(&dl_iterate_phdr_cb, &search)) - { - return NULL; - } - - if (search.base == 0 || search.load_bias == 0 || search.phdr == NULL || search.phnum == 0) - { - return NULL; - } - - return (void*)parse_soinfo(&search); -} - -__attribute__ ((visibility ("default"))) -void* npth_dlopen(const char* so_name) -{ - return get_so_info(so_name, 0); -} - -__attribute__ ((visibility ("default"))) -void* npth_dlopen_full(const char* so_name) -{ - return get_so_info(so_name, 1); -} - -__attribute__ ((visibility ("default"))) -void* npth_dlsym(void* handle, const char* sym_str) -{ - if (handle == NULL || sym_str == NULL) return NULL; - - nsoinfo_t *si = (nsoinfo_t *) handle; - - uint32_t index = get_sym_index(si, sym_str); - - if (index != 0 && (si->dynsym + index)->st_shndx != SHN_UNDEF) - { - return (void*)(si->load_bias + (si->dynsym + index)->st_value); - } - return NULL; -} - -static int load_symtab(nsoinfo_t *si) -{ - int fd = open(si->path, O_RDONLY | O_CLOEXEC); - if (fd < 0) - { - return -1; - } - - off_t size = lseek(fd,0L,SEEK_END); - - lseek(fd,0L,SEEK_SET); - - ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *) si->base; - - if (size > 0 && (size_t)(ehdr->e_shoff + ehdr->e_shentsize * ehdr->e_shnum) > (size_t)size) - { - close(fd); - return -2; - } - - size_t sh_map_size = (ehdr->e_shentsize * ehdr->e_shnum + (ehdr->e_shoff & 0xffful) + 0xfff) / 0x1000 * 0x1000; - - void* sh_map = mmap(NULL, sh_map_size, PROT_READ, MAP_PRIVATE, fd, (off_t)(ehdr->e_shoff & (~0xffful))); - - if (MAP_FAILED == sh_map) goto out; - - ElfW(Shdr) *section_header = (ElfW(Shdr) *) ((ElfW(Addr))sh_map + (ehdr->e_shoff & 0xffful)); - - ElfW(Shdr) *shdr; - ElfW(Shdr) *symtab_shdr = NULL; - ElfW(Shdr) *strtab_shdr = NULL; - - for (size_t i = 0; i < ehdr->e_shnum; i ++) - { - if (i == (size_t)ehdr->e_shstrndx) continue; - - shdr = section_header + i; - - if (NULL == shdr) continue; - - if (shdr->sh_link >= ehdr->e_shnum) continue; - - if (SHT_SYMTAB == shdr->sh_type && shdr->sh_addr == 0) - { - symtab_shdr = shdr; - } - else if(SHT_STRTAB == shdr->sh_type && shdr->sh_addr == 0) - { - strtab_shdr = shdr; - } - } - - if (symtab_shdr != NULL && strtab_shdr != NULL) - { - ElfW(Off) map_off; - size_t map_size; - - if (symtab_shdr->sh_offset < strtab_shdr->sh_offset) - { - map_off = symtab_shdr->sh_offset; - map_size = (size_t)(strtab_shdr->sh_offset - symtab_shdr->sh_offset) + strtab_shdr->sh_size; - } - else - { - map_off = strtab_shdr->sh_offset; - map_size = (size_t)(symtab_shdr->sh_offset - strtab_shdr->sh_offset) + strtab_shdr->sh_size; - } - - si->mapsize = (map_size + (map_off & 0xffful) + 0xfff) / 0x1000 * 0x1000; - - void* mem = mmap(NULL, si->mapsize, PROT_READ, MAP_PRIVATE, fd, (off_t)(map_off & (~0xffful))); - if (MAP_FAILED == mem) goto out; - - si->mapdata = mem; - - si->symtab = (ElfW(Sym) *)((ElfW(Addr))si->mapdata + (map_off & 0xffful) + - (ElfW(Addr))(symtab_shdr->sh_offset - map_off)); - si->nsymtab = symtab_shdr->sh_size / symtab_shdr->sh_entsize; - - - si->strtab = (const char*)((ElfW(Addr))si->mapdata + (map_off & 0xffful) + - (ElfW(Addr))(strtab_shdr->sh_offset - map_off)); - si->nstrtab = strtab_shdr->sh_size; - } - - out: - if (fd > 0) close(fd); - - if (MAP_FAILED != sh_map) munmap(sh_map, sh_map_size); - - return 0; -} - -__attribute__ ((visibility ("default"))) -void* npth_dlsym_full_with_size(void* handle, const char* sym_str, size_t* sym_size_ptr) -{ - if (handle == NULL || sym_str == NULL) return NULL; - - nsoinfo_t *si = (nsoinfo_t *) handle; - - if (NULL == si->path) - { - return NULL; - } - - if (si->mapdata == 0) load_symtab(si); - - if (si->symtab == NULL || si->strtab == NULL) - { - return NULL; - } - - void* addr = NULL; - - for (size_t i = 0; i < si->nsymtab; i++) - { - const ElfW(Sym)* s = si->symtab + i; - - if (s == NULL) break; - - if (s->st_shndx != SHN_UNDEF && strcmp(si->strtab +s->st_name, sym_str) == 0) - { - if (NULL != sym_size_ptr) - { - *sym_size_ptr = s->st_size; - } - addr = (void*)(si->load_bias + s->st_value); - break; - } - } - return addr; -} - -__attribute__ ((visibility ("default"))) -void* npth_dlsym_full(void* handle, const char* sym_str) -{ - return npth_dlsym_full_with_size(handle, sym_str, NULL); -} - - -__attribute__ ((visibility ("default"))) -void npth_dlclose(void* handle) { - free_soinfo((nsoinfo_t *)handle); -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wcast-align" - -static int elf_read(int fd, ElfW(Off) off, void* buf, size_t cnt) -{ - if ((off_t)off != (off_t)lseek(fd, (off_t)off, SEEK_SET)) - { - return -1; - } - - if ((ssize_t)cnt != read(fd, buf, cnt)) - { - return -1; - } - - return 0; -} -#define MAX_BUILD_ID_NUM 160 -#define NOTE_ALIGN(size) (((size) + 3ul) & ~3ul) - -typedef struct { - const char* path; // in - char* buildid; // out -} iterate_arg_t; - -static int dl_iterate_phdr_cb_for_build_id(struct dl_phdr_info* info, size_t size, void* data) -{ - if (info->dlpi_name == NULL) return 0; - iterate_arg_t* arg = (iterate_arg_t *)data; - - const char* cmp1 = arg->path; - const char* cmp2 = info->dlpi_name; - - if ('/' == cmp1[0] && '/' != cmp2[0]) - { - cmp1 = strrchr(cmp1, '/') + 1; - } - else if ('/' != cmp1[0] && '/' == cmp2[0]) - { - cmp2 = strrchr(cmp2, '/') + 1; - } - - if (0 != strcmp(cmp1, cmp2)) return 0; - - const ElfW(Phdr) *phdr = info->dlpi_phdr; - const ElfW(Phdr) *phdr_end = phdr + info->dlpi_phnum; - - ElfW(Addr) base = get_so_base(info); - - for (phdr = info->dlpi_phdr; phdr < phdr_end; phdr++) - { - if(phdr->p_type != PT_NOTE) continue; - - const ElfW(Nhdr) *nhdr = (const ElfW(Nhdr) *)(base + phdr->p_offset); - const char *nhdr_end = ((const char*)nhdr + phdr->p_memsz - sizeof(*nhdr)); - - while((const char*)nhdr < nhdr_end) - { - if (nhdr->n_type != NT_GNU_BUILD_ID) { - nhdr = (const ElfW(Nhdr) *) ((const char *) nhdr + sizeof(*nhdr) + - NOTE_ALIGN(nhdr->n_namesz) + - NOTE_ALIGN(nhdr->n_descsz)); - continue; - } - - if (nhdr->n_descsz > MAX_BUILD_ID_NUM) return -1; - - char* build_id = malloc(2 * nhdr->n_descsz + 1); - if (build_id == NULL) return -1; - - const char* addr = (const char*)nhdr + sizeof(*nhdr) + NOTE_ALIGN(nhdr->n_namesz); - - for(size_t i = 0; i < nhdr->n_descsz; i++) - { - sprintf(build_id + 2 * i, "%02hhx", addr[i]); - } - - build_id[2 * nhdr->n_descsz] = 0; - arg->buildid = build_id; - return 1; - } - } - return -1; -} - -static char* get_buildid_from_memory(const char* path) -{ - DL_ITERATE_PHDR dl_iterater = (DL_ITERATE_PHDR)npth_dliterater(); - - if (dl_iterater == NULL) - { - return NULL; - } - iterate_arg_t iterate_args = {path, NULL}; - - if (1 != dl_iterater(&dl_iterate_phdr_cb_for_build_id, &iterate_args)) - { - return NULL; - } - return iterate_args.buildid; -} - -static char* get_buildid_from_file(const char* path) -{ - if (NULL == path) return NULL; - - int fd = open(path, O_RDONLY | O_CLOEXEC); - if (fd < 0) - { - return NULL; - } - - char* build_id = NULL; - - ElfW(Ehdr) ehdr; - - if (0 != elf_read(fd, 0, &ehdr, sizeof(ehdr))) goto out; - -#define MAX_SECTION_HEEADER_NUM 64 - if (ehdr.e_shnum > MAX_SECTION_HEEADER_NUM) - { - goto out; - } - - ElfW(Shdr) shdr_tab[MAX_SECTION_HEEADER_NUM]; - - if (0 != elf_read(fd, ehdr.e_shoff, &shdr_tab, ehdr.e_shnum*sizeof(ElfW(Shdr)))) goto out; - - for (size_t i = 0; i < ehdr.e_shnum; i ++) - { - if (shdr_tab[i].sh_type != SHT_NOTE) continue; - if (shdr_tab[i].sh_size < sizeof(ElfW(Nhdr))) continue; - - ElfW(Off) name_off = shdr_tab[ehdr.e_shstrndx].sh_offset + shdr_tab[i].sh_name; - - char name_buf[sizeof(".note.gnu.build-id")]; - - if (0 != elf_read(fd, name_off, name_buf, sizeof(name_buf))) goto out; - - if (0 != strcmp(name_buf, ".note.gnu.build-id")) continue; - - ElfW(Nhdr) nhdr; - - if (0 != elf_read(fd, shdr_tab[i].sh_offset, &nhdr, sizeof(nhdr))) goto out; - - if (0 == nhdr.n_descsz || nhdr.n_descsz > shdr_tab[i].sh_size) continue; - - if (nhdr.n_descsz > MAX_BUILD_ID_NUM) continue; - - build_id = (char *)malloc(nhdr.n_descsz*2 + 1); - - ElfW(Off) id_offset = shdr_tab[i].sh_offset + sizeof(nhdr) + NOTE_ALIGN(nhdr.n_namesz); - - char id_buf[MAX_BUILD_ID_NUM]; - - if (0 != elf_read(fd, id_offset, id_buf, nhdr.n_descsz)) goto out; - - for (i = 0; i < nhdr.n_descsz; i++) - { - sprintf(build_id + 2 * i, "%02hhx", id_buf[i]); - } - build_id[2 * nhdr.n_descsz] = 0; - break; - } - out: - if (fd > 0) close(fd); - - return build_id; -} - -__attribute__ ((visibility ("default"))) -char* npth_dlbuildid(const char* path) -{ - if (path == NULL) return NULL; - - char* build_id = get_buildid_from_memory(path); - - if (build_id == NULL) build_id = get_buildid_from_file(path); - - return build_id; -} - -#ifndef __LP64__ - -typedef struct link_map link_map_t; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpadded" - -typedef struct ksoinfo { - char name[128]; - const void* phdr; - size_t phnum; - void* entry; - void* base; - unsigned size; - - uint32_t unused1; - void* dynamic; - - uint32_t unused2; - uint32_t unused3; - - struct ksoinfo* next; - unsigned flags; - - const char* strtab; - void* symtab; - - size_t nbucket; - size_t nchain; - unsigned* bucket; - unsigned* chain; - - unsigned* plt_got; - void* plt_rel; - size_t plt_rel_count; - - void* rel; - size_t rel_count; - - void* preinit_array; - size_t preinit_array_count; - - void* init_array; - size_t init_array_count; - void* fini_array; - size_t fini_array_count; - - void* init_func; - void* fini_func; - - unsigned* ARM_exidx; - size_t ARM_exidx_count; - - size_t ref_count; - link_map_t link_map; - - char constructors_called; - - void* load_bias; - - char has_text_relocations; - char has_DT_SYMBOLIC; -} ksoinfo_t; -#pragma clang diagnostic pop - -#define is_dl_info(_si) ((_si)->flags == 1 && (_si)->strtab != 0 && (_si)->symtab != 0 \ -&& (_si)->nbucket == 1 && (_si)->nchain == 8 && (_si)->bucket != 0 && (_si)->chain != 0 \ -&& (_si)->constructors_called == 0 && (_si)->has_text_relocations == 0 \ -&& (_si)->has_DT_SYMBOLIC == 1 && (_si)->load_bias == 0) - -static int k_dl_iterate_phdr(int (*cb)(struct dl_phdr_info *info, size_t size, void *data), void *data) -{ - ksoinfo_t* solist = dlopen(NULL, RTLD_LOCAL); - - if (solist == NULL) return 1; - - int rv = 0; - for (ksoinfo_t* si = solist; si != NULL; si = si->next) { - struct dl_phdr_info dl_info; - dl_info.dlpi_addr = si->link_map.l_addr; - dl_info.dlpi_name = si->link_map.l_name; - dl_info.dlpi_phdr = si->phdr; - dl_info.dlpi_phnum = (ElfW(Half))si->phnum; - rv = cb(&dl_info, sizeof(struct dl_phdr_info), data); - if (rv != 0) { - break; - } - } - - dlclose(solist); - - return rv; -} - -#endif - -__attribute__ ((visibility ("default"))) -void* npth_dliterater(void) -{ - void* handle = dlopen("libdl.so", RTLD_NOW); - - if (NULL == handle) return NULL; - - void *iterate = dlsym(handle, "dl_iterate_phdr"); - -#ifndef __LP64__ - if (iterate == NULL /* && api_level < 21 */) - { - struct ksoinfo* si = (struct ksoinfo *)handle; - if(is_dl_info(si)) - { - return (void*)&k_dl_iterate_phdr; - } - } -#endif - dlclose(handle); - - return iterate; -} - -#pragma clang diagnostic pop - -char* get_routine_so_path(size_t routine, size_t* start, size_t* end) { - char *path = NULL; - char line[1024] = {0}; - char seg_addr[64] = {0}; - FILE *f = fopen("/proc/self/maps", "r"); - if (f) { - while (fgets(line, sizeof(line), f)) { - if (1 == sscanf(line, "%[^ ]", seg_addr)) { - char* seperator = strchr(seg_addr, '-'); - if (seperator) { - *seperator = 0; - } else { - continue; - } - *start = strtoll(seg_addr, NULL, 16); - *end = strtoll(seperator+1, NULL, 16); - if (routine >= *start && routine <= *end) { - path = strchr(line, '/'); - if (path) { - char* last = strrchr(path, ' '); - if (NULL == last) { - last = strrchr(path, '\n'); - } - if (last) { - *last = '\0'; - } - } - break; - } else { - *start = 0; - *end = 0; - } - } - } - fclose(f); - } - if (path) { - return strdup(path); - } else { - return NULL; - } -} diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/npth_dl.h b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/npth_dl.h deleted file mode 100644 index baea8ec..0000000 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/npth_dl.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2021 ByteDance Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// -// Created by puyingmin on 2019/12/18. -// - -#ifndef NPTH_DL_H -#define NPTH_DL_H - -#ifdef __cplusplus -extern "C" { -#endif -extern void* npth_dlopen(const char* so_name); -extern void* npth_dlsym(void* handle, const char* sym_str); -extern void* npth_dlopen_full(const char* so_name); -extern void* npth_dlsym_full(void* handle, const char* sym_str); -extern void* npth_dlsym_full_with_size(void* handle, const char* sym_str, size_t* sym_size_ptr); -extern void npth_dlclose(void* handle); -extern void* npth_dliterater(void); -extern char* npth_dlbuildid(const char*); - -extern char* get_routine_so_path(size_t routine, size_t* start, size_t* end); -extern char* get_path_from_maps(const void* address); - -#ifdef __cplusplus -} -#endif - -#endif //NPTH_DL_H \ No newline at end of file diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/scoped_dlopen.h b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/scoped_dlopen.h index f83e328..aef18cd 100644 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/scoped_dlopen.h +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/scoped_dlopen.h @@ -15,15 +15,14 @@ */ #pragma once - -#include "npth_dl.h" +#include namespace rheatrace { class ScopedDlopen { public: ScopedDlopen(const char *so_name) { - handler = npth_dlopen(so_name); + handler = shadowhook_dlopen(so_name); } ~ScopedDlopen() { @@ -32,7 +31,7 @@ class ScopedDlopen { void release() { if (handler) { - npth_dlclose(handler); + shadowhook_dlclose(handler); } handler = nullptr; } From 1b42d2739a13d880a9bc577d8452b7aa886819d5 Mon Sep 17 00:00:00 2001 From: "yucong.park" Date: Thu, 15 Jan 2026 19:53:58 +0800 Subject: [PATCH 3/6] rename executableClass --- .../rhea-inhouse/src/main/cpp/utils/JNIHook.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/JNIHook.cpp b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/JNIHook.cpp index 908819c..2e6653f 100644 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/JNIHook.cpp +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/JNIHook.cpp @@ -26,8 +26,8 @@ static int jniEntranceIndex_ = -1; void init(JNIEnv* env, jobject foo, void* fooJNI) { void** fooArtMethod; if (android_get_device_api_level() >= 30) { - jclass Executable = env->FindClass("java/lang/reflect/Executable"); - jfieldID artMethodField = env->GetFieldID(Executable, "artMethod", "J"); + jclass executableClass = env->FindClass("java/lang/reflect/Executable"); + jfieldID artMethodField = env->GetFieldID(executableClass, "artMethod", "J"); fooArtMethod = (void**) env->GetLongField(foo, artMethodField); } else { fooArtMethod = (void**) env->FromReflectedMethod(foo); @@ -47,8 +47,8 @@ static void hookJNIMethod(JNIEnv* env, jobject method, void* newEntrance, void** } void** targetArtMethod; if (android_get_device_api_level() >= 30) { - jclass Executable = env->FindClass("java/lang/reflect/Executable"); - jfieldID artMethodField = env->GetFieldID(Executable, "artMethod", "J"); + jclass executableClass = env->FindClass("java/lang/reflect/Executable"); + jfieldID artMethodField = env->GetFieldID(executableClass, "artMethod", "J"); targetArtMethod = (void**) env->GetLongField(method, artMethodField); } else { targetArtMethod = (void**) env->FromReflectedMethod(method); @@ -77,8 +77,8 @@ hookJNIMethodId(JNIEnv* env, jclass cls, jmethodID methodId, bool isStatic, void void** targetArtMethod; if (android_get_device_api_level() >= 30) { auto method = env->ToReflectedMethod(cls, methodId, isStatic); - jclass Executable = env->FindClass("java/lang/reflect/Executable"); - jfieldID artMethodField = env->GetFieldID(Executable, "artMethod", "J"); + jclass executableClass = env->FindClass("java/lang/reflect/Executable"); + jfieldID artMethodField = env->GetFieldID(executableClass, "artMethod", "J"); targetArtMethod = (void**) env->GetLongField(method, artMethodField); } else { targetArtMethod = (void**) methodId; @@ -125,8 +125,8 @@ void* getStaticEntrance(JNIEnv* env, const char* className, const char* methodNa void** targetArtMethod; if (android_get_device_api_level() >= 30) { auto method = env->ToReflectedMethod(cls, methodId, true); - jclass Executable = env->FindClass("java/lang/reflect/Executable"); - jfieldID artMethodField = env->GetFieldID(Executable, "artMethod", "J"); + jclass executableClass = env->FindClass("java/lang/reflect/Executable"); + jfieldID artMethodField = env->GetFieldID(executableClass, "artMethod", "J"); targetArtMethod = (void**) env->GetLongField(method, artMethodField); } else { targetArtMethod = (void**) methodId; From 825b51109dd2ca60e231688c0ccc9578f95b44b3 Mon Sep 17 00:00:00 2001 From: "yucong.park" Date: Thu, 15 Jan 2026 20:12:53 +0800 Subject: [PATCH 4/6] fix "Heap::WaitForGcToCompleteLocked" proxy params --- .../rhea-inhouse/src/main/cpp/RheaOnLoad.cpp | 6 ++++-- .../rhea-inhouse/src/main/cpp/trace/TraceGC.cpp | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/RheaOnLoad.cpp b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/RheaOnLoad.cpp index 470d2f5..7a42882 100644 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/RheaOnLoad.cpp +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/RheaOnLoad.cpp @@ -18,9 +18,11 @@ namespace { + //todo: direct reflect to "setHiddenApiExemptions" not work on some devices + //todo: replace with new impl static void free_java_reflections(JNIEnv *env) { - static const char *VMRuntime_class_name = "dalvik/system/VMRuntime"; - jclass vmRuntime_class = env->FindClass(VMRuntime_class_name); + static const char *vmRuntime_class_name = "dalvik/system/VMRuntime"; + jclass vmRuntime_class = env->FindClass(vmRuntime_class_name); void *getRuntime_art_method = env->GetStaticMethodID(vmRuntime_class, "getRuntime", "()Ldalvik/system/VMRuntime;"); diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/TraceGC.cpp b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/TraceGC.cpp index abde707..6dbdc2e 100644 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/TraceGC.cpp +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/TraceGC.cpp @@ -22,11 +22,21 @@ namespace rheatrace { -static void* proxyWaitForGcToCompleteLocked(void* proxy, void* cause, void* self) { + //param3 is "bool only_one" on android 15+ +static void* proxyWaitForGcToCompleteLocked(void* heap_this, + void* cause, + void* self, + uint64_t param3, + uint64_t param4) { SHADOWHOOK_STACK_SCOPE(); uint64_t beginNano = current_boot_time_nanos(); uint64_t beginCpuNano = current_thread_cpu_time_nanos(); - void* result = SHADOWHOOK_CALL_PREV(proxyWaitForGcToCompleteLocked, proxy, cause, self); + void* result = SHADOWHOOK_CALL_PREV(proxyWaitForGcToCompleteLocked, + heap_this, + cause, + self, + param3, + param4); SamplingCollector::request(SamplingType::kGC, self, true, true, beginNano, beginCpuNano); return result; } From d5ebaedf288a1a2035c0e0f82e25b97e686395e0 Mon Sep 17 00:00:00 2001 From: Cao Dongping Date: Thu, 15 Jan 2026 20:30:12 +0800 Subject: [PATCH 5/6] Revert "replace npth_dl with shadowhook internal xdl" --- btrace-android/app/build.gradle | 1 + .../sample/android/ExampleInstrumentedTest.kt | 50 + .../rhea/sample/android/ExampleUnitTest.kt | 31 + .../rhea-inhouse/src/main/cpp/CMakeLists.txt | 1 + .../rhea-inhouse/src/main/cpp/RheaOnLoad.cpp | 6 +- .../src/main/cpp/sampling/Stack.cpp | 4 +- .../src/main/cpp/sampling/StackVisitor.cpp | 20 +- .../src/main/cpp/trace/TraceGC.cpp | 14 +- .../cpp/trace/java_alloc/TraceJavaAlloc.cpp | 13 +- .../main/cpp/trace/java_alloc/checkpoint.cpp | 5 +- .../src/main/cpp/utils/JNIHook.cpp | 16 +- .../rhea-inhouse/src/main/cpp/utils/npth_dl.c | 976 ++++++++++++++++++ .../rhea-inhouse/src/main/cpp/utils/npth_dl.h | 42 + .../src/main/cpp/utils/scoped_dlopen.h | 7 +- 14 files changed, 1140 insertions(+), 46 deletions(-) create mode 100644 btrace-android/app/src/androidTest/java/rhea/sample/android/ExampleInstrumentedTest.kt create mode 100644 btrace-android/app/src/test/java/rhea/sample/android/ExampleUnitTest.kt create mode 100644 btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/npth_dl.c create mode 100644 btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/npth_dl.h diff --git a/btrace-android/app/build.gradle b/btrace-android/app/build.gradle index a0eb7e1..659599b 100644 --- a/btrace-android/app/build.gradle +++ b/btrace-android/app/build.gradle @@ -11,6 +11,7 @@ android { targetSdkVersion versions.targetSdk versionCode 1 versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" ndk { abiFilters "arm64-v8a", "armeabi-v7a" diff --git a/btrace-android/app/src/androidTest/java/rhea/sample/android/ExampleInstrumentedTest.kt b/btrace-android/app/src/androidTest/java/rhea/sample/android/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..7302f04 --- /dev/null +++ b/btrace-android/app/src/androidTest/java/rhea/sample/android/ExampleInstrumentedTest.kt @@ -0,0 +1,50 @@ +package rhea.sample.android + +import android.util.Log +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.bytedance.rheatrace.core.TraceStub + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + + companion object { + const val TAG: String = "Rhea:AndroidTest" + } + + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("rhea.sample.android", appContext.packageName) + } + + @Test + fun testTraceStub() { + val testCounts = 10 + val loopCount = 1000000 + + val ids: Array = arrayOfNulls(loopCount) + for (i in 0 until loopCount) { + ids[i] = i.toString() + } + for (time in 0 until testCounts) { + val start = System.currentTimeMillis() + for (id in ids) { + TraceStub.i(id) + TraceStub.o(id) + } + Log.d(TAG, "unit test cost: " + (System.currentTimeMillis() - start) + "ms") + } + } +} \ No newline at end of file diff --git a/btrace-android/app/src/test/java/rhea/sample/android/ExampleUnitTest.kt b/btrace-android/app/src/test/java/rhea/sample/android/ExampleUnitTest.kt new file mode 100644 index 0000000..477a1b8 --- /dev/null +++ b/btrace-android/app/src/test/java/rhea/sample/android/ExampleUnitTest.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 ByteDance Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rhea.sample.android + +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/CMakeLists.txt b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/CMakeLists.txt index ae152b2..9094b48 100644 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/CMakeLists.txt +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/CMakeLists.txt @@ -19,6 +19,7 @@ set(RHEA_SRCS trace/java_alloc/thread_list.cpp trace/java_alloc/TraceJavaAlloc.cpp utils/JNIHook.cpp + utils/npth_dl.c TraceGlobalJni.cpp TraceAbilityJni.cpp RheaOnLoad.cpp diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/RheaOnLoad.cpp b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/RheaOnLoad.cpp index 7a42882..470d2f5 100644 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/RheaOnLoad.cpp +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/RheaOnLoad.cpp @@ -18,11 +18,9 @@ namespace { - //todo: direct reflect to "setHiddenApiExemptions" not work on some devices - //todo: replace with new impl static void free_java_reflections(JNIEnv *env) { - static const char *vmRuntime_class_name = "dalvik/system/VMRuntime"; - jclass vmRuntime_class = env->FindClass(vmRuntime_class_name); + static const char *VMRuntime_class_name = "dalvik/system/VMRuntime"; + jclass vmRuntime_class = env->FindClass(VMRuntime_class_name); void *getRuntime_art_method = env->GetStaticMethodID(vmRuntime_class, "getRuntime", "()Ldalvik/system/VMRuntime;"); diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/sampling/Stack.cpp b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/sampling/Stack.cpp index 0e5d797..39f3ada 100644 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/sampling/Stack.cpp +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/sampling/Stack.cpp @@ -16,7 +16,7 @@ #include "Stack.h" #include "../base/common_write.h" -#include +#include "../utils/npth_dl.h" #include #include @@ -35,7 +35,7 @@ bool Stack::init(void* libartHandle) { } if (libartHandle != nullptr) { - sPrettyMethodCall = reinterpret_cast(shadowhook_dlsym(libartHandle, ART_METHOD_PRETTY_METHOD)); + sPrettyMethodCall = reinterpret_cast(npth_dlsym(libartHandle, ART_METHOD_PRETTY_METHOD)); sInited = sPrettyMethodCall != nullptr; } diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/sampling/StackVisitor.cpp b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/sampling/StackVisitor.cpp index ee78ee1..687cead 100644 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/sampling/StackVisitor.cpp +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/sampling/StackVisitor.cpp @@ -14,9 +14,11 @@ * limitations under the License. */ #include "StackVisitor.h" -#include +#include "../utils/npth_dl.h" + #include + namespace rheatrace { static constexpr const char* THREAD_CURRENT_FROM_GDB = "_ZN3art6Thread14CurrentFromGdbEv"; @@ -52,22 +54,22 @@ bool StackVisitor::init() { if (sInited) { return true; } - auto *handle = shadowhook_dlopen("libart.so"); + auto *handle = npth_dlopen("libart.so"); if (handle == nullptr) { return false; } - sCurrentThreadCall = reinterpret_cast(shadowhook_dlsym(handle, THREAD_CURRENT_FROM_GDB)); - sConstructCall = reinterpret_cast(shadowhook_dlsym(handle, STACK_VISITOR_CTOR)); - sDestructCall = reinterpret_cast(shadowhook_dlsym(handle, STACK_VISITOR_DTOR)); - sCreateContextCall = reinterpret_cast(shadowhook_dlsym(handle, CONTEXT_CREATE)); - sGetMethodCall = reinterpret_cast(shadowhook_dlsym(handle, STACK_VISITOR_GET_METHOD)); - sWalkStackCall = reinterpret_cast(shadowhook_dlsym(handle, STACK_VISITOR_WALK_STACK)); + sCurrentThreadCall = reinterpret_cast(npth_dlsym(handle, THREAD_CURRENT_FROM_GDB)); + sConstructCall = reinterpret_cast(npth_dlsym(handle, STACK_VISITOR_CTOR)); + sDestructCall = reinterpret_cast(npth_dlsym(handle, STACK_VISITOR_DTOR)); + sCreateContextCall = reinterpret_cast(npth_dlsym(handle, CONTEXT_CREATE)); + sGetMethodCall = reinterpret_cast(npth_dlsym(handle, STACK_VISITOR_GET_METHOD)); + sWalkStackCall = reinterpret_cast(npth_dlsym(handle, STACK_VISITOR_WALK_STACK)); if (sConstructCall != nullptr && sCreateContextCall != nullptr && sGetMethodCall != nullptr && sWalkStackCall != nullptr) { sInited = Stack::init(handle); } - shadowhook_dlclose(handle); + npth_dlclose(handle); return sInited; } diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/TraceGC.cpp b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/TraceGC.cpp index 6dbdc2e..abde707 100644 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/TraceGC.cpp +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/TraceGC.cpp @@ -22,21 +22,11 @@ namespace rheatrace { - //param3 is "bool only_one" on android 15+ -static void* proxyWaitForGcToCompleteLocked(void* heap_this, - void* cause, - void* self, - uint64_t param3, - uint64_t param4) { +static void* proxyWaitForGcToCompleteLocked(void* proxy, void* cause, void* self) { SHADOWHOOK_STACK_SCOPE(); uint64_t beginNano = current_boot_time_nanos(); uint64_t beginCpuNano = current_thread_cpu_time_nanos(); - void* result = SHADOWHOOK_CALL_PREV(proxyWaitForGcToCompleteLocked, - heap_this, - cause, - self, - param3, - param4); + void* result = SHADOWHOOK_CALL_PREV(proxyWaitForGcToCompleteLocked, proxy, cause, self); SamplingCollector::request(SamplingType::kGC, self, true, true, beginNano, beginCpuNano); return result; } diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/java_alloc/TraceJavaAlloc.cpp b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/java_alloc/TraceJavaAlloc.cpp index 4f07a41..b773adf 100644 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/java_alloc/TraceJavaAlloc.cpp +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/java_alloc/TraceJavaAlloc.cpp @@ -20,6 +20,7 @@ #include "TraceJavaAlloc.h" #include +#include "../../utils/npth_dl.h" #include #include #include "java_alloc_common.h" @@ -253,7 +254,7 @@ void registerAllocationListener() { } bool TraceJavaAlloc::init() { - std::shared_ptr scope = std::shared_ptr(shadowhook_dlopen("libart.so"), shadowhook_dlclose); + std::shared_ptr scope = std::shared_ptr(npth_dlopen("libart.so"), npth_dlclose); if (scope.get() == nullptr) { ALOGE("Cannot open libart.so"); return false; @@ -264,13 +265,13 @@ bool TraceJavaAlloc::init() { return false; } - SetAllocationListenerFunc = (SetPtr_) shadowhook_dlsym(art_lib, + SetAllocationListenerFunc = (SetPtr_) npth_dlsym(art_lib, HEAP_SET_ALLOC_LISTENER); if(SetAllocationListenerFunc == nullptr) { ALOGE("Cannot find SetAllocationListener"); return false; } - RemoveAllocationListenerFunc = (CallVoid_) shadowhook_dlsym(art_lib, + RemoveAllocationListenerFunc = (CallVoid_) npth_dlsym(art_lib, HEAP_REMOVE_ALLOC_LISTENER); if(RemoveAllocationListenerFunc == nullptr) { ALOGE("Cannot find RemoveAllocationListener"); @@ -278,11 +279,11 @@ bool TraceJavaAlloc::init() { } s_use_thread_ResetQuickAllocEntryPointsForThread_bool = false; - Thread_ResetQuickAllocEntryPointsForThreadFunc = shadowhook_dlsym(art_lib, + Thread_ResetQuickAllocEntryPointsForThreadFunc = npth_dlsym(art_lib, Thread_RESET_QUICK_ALLOC_ENTRY_POINTS_FOR_THREAD); if(Thread_ResetQuickAllocEntryPointsForThreadFunc == nullptr) { s_use_thread_ResetQuickAllocEntryPointsForThread_bool = true; - Thread_ResetQuickAllocEntryPointsForThreadFunc = shadowhook_dlsym(art_lib, + Thread_ResetQuickAllocEntryPointsForThreadFunc = npth_dlsym(art_lib, Thread_RESET_QUICK_ALLOC_ENTRY_POINTS_FOR_THREAD_BOOL); } if(Thread_ResetQuickAllocEntryPointsForThreadFunc == nullptr) { @@ -290,7 +291,7 @@ bool TraceJavaAlloc::init() { return false; } - SetQuickAllocEntryPointsInstrumentedFunc = (SetQuickAllocEntryPointsInstrumented_) shadowhook_dlsym( + SetQuickAllocEntryPointsInstrumentedFunc = (SetQuickAllocEntryPointsInstrumented_) npth_dlsym( art_lib, SET_QUICK_ALLOC_ENTRY_POINTS_INSTRUMENTED); if(SetQuickAllocEntryPointsInstrumentedFunc == nullptr) { ALOGE("Cannot find SetQuickAllocEntryPointsInstrumented"); diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/java_alloc/checkpoint.cpp b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/java_alloc/checkpoint.cpp index 662da7a..008fdda 100644 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/java_alloc/checkpoint.cpp +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/trace/java_alloc/checkpoint.cpp @@ -20,6 +20,7 @@ #include "checkpoint.h" #include #include +#include "../../utils/npth_dl.h" #include "java_alloc_common.h" #include "../../RheaContext.h" #include "../../utils/log.h" @@ -52,10 +53,10 @@ size_t run_checkpoint(void *closure) { bool init_checkpoint(void* lib) { - ThreadList_RunCheckpointFunc = (ThreadList_RunCheckpoint_) shadowhook_dlsym(lib, + ThreadList_RunCheckpointFunc = (ThreadList_RunCheckpoint_) npth_dlsym(lib, ThreadList_RUN_CHECKPOINT); if(ThreadList_RunCheckpointFunc == nullptr) { - ThreadList_RunCheckpointFunc_15 = (ThreadList_RunCheckpoint_15_) shadowhook_dlsym(lib, + ThreadList_RunCheckpointFunc_15 = (ThreadList_RunCheckpoint_15_) npth_dlsym(lib, ThreadList_RUN_CHECKPOINT_15); } if (ThreadList_RunCheckpointFunc == nullptr && ThreadList_RunCheckpointFunc_15 == nullptr) { diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/JNIHook.cpp b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/JNIHook.cpp index 2e6653f..908819c 100644 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/JNIHook.cpp +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/JNIHook.cpp @@ -26,8 +26,8 @@ static int jniEntranceIndex_ = -1; void init(JNIEnv* env, jobject foo, void* fooJNI) { void** fooArtMethod; if (android_get_device_api_level() >= 30) { - jclass executableClass = env->FindClass("java/lang/reflect/Executable"); - jfieldID artMethodField = env->GetFieldID(executableClass, "artMethod", "J"); + jclass Executable = env->FindClass("java/lang/reflect/Executable"); + jfieldID artMethodField = env->GetFieldID(Executable, "artMethod", "J"); fooArtMethod = (void**) env->GetLongField(foo, artMethodField); } else { fooArtMethod = (void**) env->FromReflectedMethod(foo); @@ -47,8 +47,8 @@ static void hookJNIMethod(JNIEnv* env, jobject method, void* newEntrance, void** } void** targetArtMethod; if (android_get_device_api_level() >= 30) { - jclass executableClass = env->FindClass("java/lang/reflect/Executable"); - jfieldID artMethodField = env->GetFieldID(executableClass, "artMethod", "J"); + jclass Executable = env->FindClass("java/lang/reflect/Executable"); + jfieldID artMethodField = env->GetFieldID(Executable, "artMethod", "J"); targetArtMethod = (void**) env->GetLongField(method, artMethodField); } else { targetArtMethod = (void**) env->FromReflectedMethod(method); @@ -77,8 +77,8 @@ hookJNIMethodId(JNIEnv* env, jclass cls, jmethodID methodId, bool isStatic, void void** targetArtMethod; if (android_get_device_api_level() >= 30) { auto method = env->ToReflectedMethod(cls, methodId, isStatic); - jclass executableClass = env->FindClass("java/lang/reflect/Executable"); - jfieldID artMethodField = env->GetFieldID(executableClass, "artMethod", "J"); + jclass Executable = env->FindClass("java/lang/reflect/Executable"); + jfieldID artMethodField = env->GetFieldID(Executable, "artMethod", "J"); targetArtMethod = (void**) env->GetLongField(method, artMethodField); } else { targetArtMethod = (void**) methodId; @@ -125,8 +125,8 @@ void* getStaticEntrance(JNIEnv* env, const char* className, const char* methodNa void** targetArtMethod; if (android_get_device_api_level() >= 30) { auto method = env->ToReflectedMethod(cls, methodId, true); - jclass executableClass = env->FindClass("java/lang/reflect/Executable"); - jfieldID artMethodField = env->GetFieldID(executableClass, "artMethod", "J"); + jclass Executable = env->FindClass("java/lang/reflect/Executable"); + jfieldID artMethodField = env->GetFieldID(Executable, "artMethod", "J"); targetArtMethod = (void**) env->GetLongField(method, artMethodField); } else { targetArtMethod = (void**) methodId; diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/npth_dl.c b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/npth_dl.c new file mode 100644 index 0000000..6571ce7 --- /dev/null +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/npth_dl.c @@ -0,0 +1,976 @@ +/* + * Copyright (C) 2021 ByteDance Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by puyingmin on 2019/12/18. +// +#define LOG_TAG "NPTH_DL" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "npth_dl.h" + +struct elf_hash_struct +{ + size_t nbucket; + size_t nchain; + unsigned *bucket; + unsigned *chain; +}; + +struct gnu_hash_struct +{ + size_t nbucket; + uint32_t *bucket; + uint32_t *chain; + uint32_t maskwords; + uint32_t shift2; + ElfW(Addr) *bloomfilter; +}; + +#define ELF_HASH_MASK (uint32_t)0x1 +#define GNU_HASH_MASK (uint32_t)0x2 +#define HAS_ELF_HASH(_flag) (((_flag) & ELF_HASH_MASK) == ELF_HASH_MASK) +#define HAS_GNU_HASH(_flag) (((_flag) & GNU_HASH_MASK) == GNU_HASH_MASK) +#define SET_ELF_HASH(_flag) do { (_flag) |= ELF_HASH_MASK; } while(0) +#define SET_GNU_HASH(_flag) do { (_flag) |= GNU_HASH_MASK; } while(0) + +typedef struct nsoinfo +{ + ElfW(Addr) base; + ElfW(Addr) load_bias; + char* path; + size_t size; + uint32_t phnum; + uint32_t hash_flag; + const ElfW(Phdr) *phdr; + const ElfW(Dyn) *dynamic; + const ElfW(Sym) *dynsym; + const char *dynstr; + + const ElfW(Sym) *symtab; + const char *strtab; + size_t nsymtab; + size_t nstrtab; + struct gnu_hash_struct gnu_hash; + struct elf_hash_struct elf_hash; + void *mapdata; + size_t mapsize; +} nsoinfo_t; + +typedef int (*DL_ITERATE_PHDR_CB)(struct dl_phdr_info* , size_t, void*); +typedef int (*DL_ITERATE_PHDR)(DL_ITERATE_PHDR_CB, void*); + +typedef struct +{ + const char* name; /* in */ + union + { + int need_path; /* in */ + void* padding1; + }; + char* path; /* out */ + ElfW(Addr) base; /* out */ + ElfW(Addr) load_bias; /* out */ + const ElfW(Phdr)* phdr; /* out */ + union + { + uint32_t phnum; /* out */ + void * padding2; + }; +} dl_search_t; + +char* get_path_from_maps(const void* address) +{ + char line[1024]; + uintptr_t start; + + FILE *f = fopen("/proc/self/maps", "r"); + if (f == NULL) return NULL; + + char * ret = NULL; + while (fgets(line, sizeof(line), f)) + { + if (1 == sscanf(line, "%"SCNxPTR"", &start) && (void *)start == address) + { + char* path = strchr(line, '/'); + if (NULL == path) break; + + char* last = strchr(path, ' '); + if (NULL == last) + { + last = strchr(path, '\n'); + if (NULL == last) break; + } + + *last = '\0'; + + ret = strdup(path); + break; + } + } + + fclose(f); + return ret; +} + +static ElfW(Addr) get_so_base(struct dl_phdr_info* info) +{ + const ElfW(Phdr) *phdr = info->dlpi_phdr; + const ElfW(Phdr) *phdr_end = phdr + info->dlpi_phnum; + + ElfW(Addr) min_vaddr = INTPTR_MAX; + + for (phdr = info->dlpi_phdr; phdr < phdr_end; phdr++) + { + if (phdr->p_type != PT_LOAD) continue; + + if (min_vaddr <= phdr->p_vaddr) continue; + + min_vaddr = phdr->p_vaddr; + } + + return min_vaddr == INTPTR_MAX ? 0 : info->dlpi_addr + min_vaddr; +} + +static int dl_iterate_phdr_cb(struct dl_phdr_info* info, size_t size, void* data) +{ + dl_search_t* search = (dl_search_t *)data; + + if (info->dlpi_name == NULL) return 0; + + const char* cmp1 = search->name; + const char* cmp2 = info->dlpi_name; + + if ('/' == cmp1[0] && '/' != cmp2[0]) + { + cmp1 = strrchr(cmp1, '/') + 1; + } + else if ('/' != cmp1[0] && '/' == cmp2[0]) + { + cmp2 = strrchr(cmp2, '/') + 1; + } + + if (0 != strcmp(cmp1, cmp2)) return 0; + + search->base = get_so_base(info); + search->load_bias = info->dlpi_addr; + search->phdr = info->dlpi_phdr; + search->phnum = (uint32_t)(info->dlpi_phnum); + + if ('/' == info->dlpi_name[0]) + { + if (search->need_path) + { + search->path = strdup(info->dlpi_name); + } + } + else if ('/' == search->name[0] || search->need_path) + { + char* path = get_path_from_maps((const void *)search->base); + + if (NULL == path) return 0; + + if ('/' == search->name[0]) + { + if (0 != strcmp(search->name, path)) + { + free(path); + return 0; + } + } + + if (search->need_path) + { + search->path = path; + } + else + { + free(path); + } + } + + return 1; +} + +static uint32_t elf_hash(const char *name) +{ + const uint8_t *ptr = (const uint8_t *) name; + uint32_t h = 0; + uint32_t g; + + while (*ptr) + { + h = (h << 4) + *ptr++; + g = h & 0xf0000000; + h ^= g; + h ^= g >> 24; + } + + return h; +} + +static uint32_t gnu_hash(const char *name) +{ + uint32_t h = 5381; + const uint8_t* ptr = (const uint8_t*)(name); + + while (*ptr != 0) + { + h += (h << 5) + *ptr++; // h*33 + c = h + h * 32 + c = h + h << 5 + c + } + + return h; +} + +static uint32_t get_sym_index_with_elf_hash(nsoinfo_t* si, const char* symname) +{ + struct elf_hash_struct* hash_struct = &si->elf_hash; + + uint32_t hash = elf_hash(symname); + uint32_t idx = hash_struct->bucket[hash % hash_struct->nbucket]; + + for ( ; idx != 0; idx = hash_struct->chain[idx]) + { + const ElfW(Sym)* s = si->dynsym + idx; + + if (strcmp(si->dynstr + s->st_name, symname) == 0) { + break; + } + } + return idx; +} + +static uint32_t get_sym_index_with_gnu_hash(nsoinfo_t* si, const char* symname) +{ + struct gnu_hash_struct* hash_struct = &si->gnu_hash; + + uint32_t hash = gnu_hash(symname); + uint32_t h2 = hash >> hash_struct->shift2; + uint32_t bloom_mask = sizeof(ElfW(Addr))*8; + uint32_t word_num = (hash / bloom_mask) & hash_struct->maskwords; + ElfW(Addr) bloom_word = hash_struct->bloomfilter[word_num]; + + uint32_t n; + uint32_t idx = 0; + + if (((0x1u & (bloom_word >> (hash % bloom_mask)) & (bloom_word >> (h2 % bloom_mask))) != 0) + && ((n = hash_struct->bucket[hash % hash_struct->nbucket])!= 0)) + { + do + { + const ElfW(Sym) *s = si->dynsym + n; + if (((hash_struct->chain[n] ^ hash) >> 0x1u) == 0 && + strcmp(si->dynstr + s->st_name, symname) == 0) + { + idx = n; + break; + } + } while ((hash_struct->chain[n++] & 0x1u) == 0); + } + return idx; +} + +static uint32_t get_sym_index(nsoinfo_t* si, const char* symname) +{ + uint32_t index = 0; + + if (HAS_GNU_HASH(si->hash_flag)) + { + index = get_sym_index_with_gnu_hash(si, symname); + } + + if (index == 0 && HAS_ELF_HASH(si->hash_flag)) + { + index = get_sym_index_with_elf_hash(si, symname); + } + + return index; +} + +static void fill_elf_hash_struct(nsoinfo_t* si, ElfW(Addr) header) +{ + struct elf_hash_struct* hash_struct = &si->elf_hash; + hash_struct->nbucket = ((uint32_t*)header)[0]; + hash_struct->nchain = ((uint32_t*)header)[1]; + hash_struct->bucket = (uint32_t*)(header + 8); + hash_struct->chain = (uint32_t*)(header + 8 + hash_struct->nbucket * 4); + + SET_ELF_HASH(si->hash_flag); +} + +static void fill_gnu_hash_struct(nsoinfo_t* si, ElfW(Addr) header) +{ + struct gnu_hash_struct* hash_struct = &si->gnu_hash; + + hash_struct->nbucket = ((uint32_t*)header)[0]; + // skip symndx + hash_struct->maskwords = ((uint32_t*)header)[2]; + hash_struct->shift2 = ((uint32_t*)header)[3]; + + hash_struct->bloomfilter = (ElfW(Addr)*)(header + 16); + hash_struct->bucket = (uint32_t*)(hash_struct->bloomfilter + hash_struct->maskwords); + // amend chain for symndx = header[1] + hash_struct->chain = hash_struct->bucket + hash_struct->nbucket - ((uint32_t*)header)[1]; + + if (!powerof2(hash_struct->maskwords)) + { + return; + } + hash_struct->maskwords--; + + SET_GNU_HASH(si->hash_flag); +} + +static nsoinfo_t* alloc_soinfo() +{ + return (nsoinfo_t *)calloc(1, sizeof(nsoinfo_t)); +} + +static void free_soinfo(nsoinfo_t* si) +{ + if (si != NULL) + { + if (si->path != NULL) + { + free(si->path); + } + if (si->mapdata != 0) + { + munmap(si->mapdata, si->mapsize); + } + free(si); + } +} + +static nsoinfo_t* parse_soinfo(dl_search_t* search) +{ + nsoinfo_t* si = alloc_soinfo(); + if (si == NULL) return NULL; + + si->base = search->base; + si->phdr = search->phdr; + si->phnum = search->phnum; + si->path = search->path; + si->load_bias = search->load_bias; + si->hash_flag = 0; + + ElfW(Addr) dynamic = 0; + for(const ElfW(Phdr) *ph = si->phdr; ph < si->phdr + si->phnum; ph++) + { + if (ph->p_type == PT_DYNAMIC) + { + dynamic = ph->p_vaddr; + } + } + + if (dynamic == 0) goto err; + + si->dynamic = (ElfW(Dyn) *)(dynamic + si->load_bias); + + for (const ElfW(Dyn)* d = si->dynamic; d->d_tag != DT_NULL; ++d) + { + switch (d->d_tag) + { + case DT_HASH: + fill_elf_hash_struct(si, si->load_bias + d->d_un.d_ptr); + break; + + case DT_GNU_HASH: + fill_gnu_hash_struct(si, si->load_bias + d->d_un.d_ptr); + break; + + case DT_STRTAB: + si->dynstr = (const char *)(si->load_bias + d->d_un.d_ptr); + break; + + case DT_SYMTAB: + si->dynsym = (ElfW(Sym) *)(si->load_bias + d->d_un.d_ptr); + break; + + default: + break; + } + } + + if (si->dynsym != NULL && si->dynstr != NULL && si->hash_flag != 0) + { + return si; + } + + err: + free_soinfo(si); + + return NULL; +} + +static void* get_so_info(const char* so_name, int need_path) +{ + DL_ITERATE_PHDR dl_iterater = (DL_ITERATE_PHDR)npth_dliterater(); + + if (dl_iterater == NULL) + { + return NULL; + } + + dl_search_t search; + + memset(&search, 0 , sizeof(dl_search_t)); + + search.name = so_name; + search.need_path = need_path; + + if (1 != dl_iterater(&dl_iterate_phdr_cb, &search)) + { + return NULL; + } + + if (search.base == 0 || search.load_bias == 0 || search.phdr == NULL || search.phnum == 0) + { + return NULL; + } + + return (void*)parse_soinfo(&search); +} + +__attribute__ ((visibility ("default"))) +void* npth_dlopen(const char* so_name) +{ + return get_so_info(so_name, 0); +} + +__attribute__ ((visibility ("default"))) +void* npth_dlopen_full(const char* so_name) +{ + return get_so_info(so_name, 1); +} + +__attribute__ ((visibility ("default"))) +void* npth_dlsym(void* handle, const char* sym_str) +{ + if (handle == NULL || sym_str == NULL) return NULL; + + nsoinfo_t *si = (nsoinfo_t *) handle; + + uint32_t index = get_sym_index(si, sym_str); + + if (index != 0 && (si->dynsym + index)->st_shndx != SHN_UNDEF) + { + return (void*)(si->load_bias + (si->dynsym + index)->st_value); + } + return NULL; +} + +static int load_symtab(nsoinfo_t *si) +{ + int fd = open(si->path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + { + return -1; + } + + off_t size = lseek(fd,0L,SEEK_END); + + lseek(fd,0L,SEEK_SET); + + ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *) si->base; + + if (size > 0 && (size_t)(ehdr->e_shoff + ehdr->e_shentsize * ehdr->e_shnum) > (size_t)size) + { + close(fd); + return -2; + } + + size_t sh_map_size = (ehdr->e_shentsize * ehdr->e_shnum + (ehdr->e_shoff & 0xffful) + 0xfff) / 0x1000 * 0x1000; + + void* sh_map = mmap(NULL, sh_map_size, PROT_READ, MAP_PRIVATE, fd, (off_t)(ehdr->e_shoff & (~0xffful))); + + if (MAP_FAILED == sh_map) goto out; + + ElfW(Shdr) *section_header = (ElfW(Shdr) *) ((ElfW(Addr))sh_map + (ehdr->e_shoff & 0xffful)); + + ElfW(Shdr) *shdr; + ElfW(Shdr) *symtab_shdr = NULL; + ElfW(Shdr) *strtab_shdr = NULL; + + for (size_t i = 0; i < ehdr->e_shnum; i ++) + { + if (i == (size_t)ehdr->e_shstrndx) continue; + + shdr = section_header + i; + + if (NULL == shdr) continue; + + if (shdr->sh_link >= ehdr->e_shnum) continue; + + if (SHT_SYMTAB == shdr->sh_type && shdr->sh_addr == 0) + { + symtab_shdr = shdr; + } + else if(SHT_STRTAB == shdr->sh_type && shdr->sh_addr == 0) + { + strtab_shdr = shdr; + } + } + + if (symtab_shdr != NULL && strtab_shdr != NULL) + { + ElfW(Off) map_off; + size_t map_size; + + if (symtab_shdr->sh_offset < strtab_shdr->sh_offset) + { + map_off = symtab_shdr->sh_offset; + map_size = (size_t)(strtab_shdr->sh_offset - symtab_shdr->sh_offset) + strtab_shdr->sh_size; + } + else + { + map_off = strtab_shdr->sh_offset; + map_size = (size_t)(symtab_shdr->sh_offset - strtab_shdr->sh_offset) + strtab_shdr->sh_size; + } + + si->mapsize = (map_size + (map_off & 0xffful) + 0xfff) / 0x1000 * 0x1000; + + void* mem = mmap(NULL, si->mapsize, PROT_READ, MAP_PRIVATE, fd, (off_t)(map_off & (~0xffful))); + if (MAP_FAILED == mem) goto out; + + si->mapdata = mem; + + si->symtab = (ElfW(Sym) *)((ElfW(Addr))si->mapdata + (map_off & 0xffful) + + (ElfW(Addr))(symtab_shdr->sh_offset - map_off)); + si->nsymtab = symtab_shdr->sh_size / symtab_shdr->sh_entsize; + + + si->strtab = (const char*)((ElfW(Addr))si->mapdata + (map_off & 0xffful) + + (ElfW(Addr))(strtab_shdr->sh_offset - map_off)); + si->nstrtab = strtab_shdr->sh_size; + } + + out: + if (fd > 0) close(fd); + + if (MAP_FAILED != sh_map) munmap(sh_map, sh_map_size); + + return 0; +} + +__attribute__ ((visibility ("default"))) +void* npth_dlsym_full_with_size(void* handle, const char* sym_str, size_t* sym_size_ptr) +{ + if (handle == NULL || sym_str == NULL) return NULL; + + nsoinfo_t *si = (nsoinfo_t *) handle; + + if (NULL == si->path) + { + return NULL; + } + + if (si->mapdata == 0) load_symtab(si); + + if (si->symtab == NULL || si->strtab == NULL) + { + return NULL; + } + + void* addr = NULL; + + for (size_t i = 0; i < si->nsymtab; i++) + { + const ElfW(Sym)* s = si->symtab + i; + + if (s == NULL) break; + + if (s->st_shndx != SHN_UNDEF && strcmp(si->strtab +s->st_name, sym_str) == 0) + { + if (NULL != sym_size_ptr) + { + *sym_size_ptr = s->st_size; + } + addr = (void*)(si->load_bias + s->st_value); + break; + } + } + return addr; +} + +__attribute__ ((visibility ("default"))) +void* npth_dlsym_full(void* handle, const char* sym_str) +{ + return npth_dlsym_full_with_size(handle, sym_str, NULL); +} + + +__attribute__ ((visibility ("default"))) +void npth_dlclose(void* handle) { + free_soinfo((nsoinfo_t *)handle); +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-align" + +static int elf_read(int fd, ElfW(Off) off, void* buf, size_t cnt) +{ + if ((off_t)off != (off_t)lseek(fd, (off_t)off, SEEK_SET)) + { + return -1; + } + + if ((ssize_t)cnt != read(fd, buf, cnt)) + { + return -1; + } + + return 0; +} +#define MAX_BUILD_ID_NUM 160 +#define NOTE_ALIGN(size) (((size) + 3ul) & ~3ul) + +typedef struct { + const char* path; // in + char* buildid; // out +} iterate_arg_t; + +static int dl_iterate_phdr_cb_for_build_id(struct dl_phdr_info* info, size_t size, void* data) +{ + if (info->dlpi_name == NULL) return 0; + iterate_arg_t* arg = (iterate_arg_t *)data; + + const char* cmp1 = arg->path; + const char* cmp2 = info->dlpi_name; + + if ('/' == cmp1[0] && '/' != cmp2[0]) + { + cmp1 = strrchr(cmp1, '/') + 1; + } + else if ('/' != cmp1[0] && '/' == cmp2[0]) + { + cmp2 = strrchr(cmp2, '/') + 1; + } + + if (0 != strcmp(cmp1, cmp2)) return 0; + + const ElfW(Phdr) *phdr = info->dlpi_phdr; + const ElfW(Phdr) *phdr_end = phdr + info->dlpi_phnum; + + ElfW(Addr) base = get_so_base(info); + + for (phdr = info->dlpi_phdr; phdr < phdr_end; phdr++) + { + if(phdr->p_type != PT_NOTE) continue; + + const ElfW(Nhdr) *nhdr = (const ElfW(Nhdr) *)(base + phdr->p_offset); + const char *nhdr_end = ((const char*)nhdr + phdr->p_memsz - sizeof(*nhdr)); + + while((const char*)nhdr < nhdr_end) + { + if (nhdr->n_type != NT_GNU_BUILD_ID) { + nhdr = (const ElfW(Nhdr) *) ((const char *) nhdr + sizeof(*nhdr) + + NOTE_ALIGN(nhdr->n_namesz) + + NOTE_ALIGN(nhdr->n_descsz)); + continue; + } + + if (nhdr->n_descsz > MAX_BUILD_ID_NUM) return -1; + + char* build_id = malloc(2 * nhdr->n_descsz + 1); + if (build_id == NULL) return -1; + + const char* addr = (const char*)nhdr + sizeof(*nhdr) + NOTE_ALIGN(nhdr->n_namesz); + + for(size_t i = 0; i < nhdr->n_descsz; i++) + { + sprintf(build_id + 2 * i, "%02hhx", addr[i]); + } + + build_id[2 * nhdr->n_descsz] = 0; + arg->buildid = build_id; + return 1; + } + } + return -1; +} + +static char* get_buildid_from_memory(const char* path) +{ + DL_ITERATE_PHDR dl_iterater = (DL_ITERATE_PHDR)npth_dliterater(); + + if (dl_iterater == NULL) + { + return NULL; + } + iterate_arg_t iterate_args = {path, NULL}; + + if (1 != dl_iterater(&dl_iterate_phdr_cb_for_build_id, &iterate_args)) + { + return NULL; + } + return iterate_args.buildid; +} + +static char* get_buildid_from_file(const char* path) +{ + if (NULL == path) return NULL; + + int fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + { + return NULL; + } + + char* build_id = NULL; + + ElfW(Ehdr) ehdr; + + if (0 != elf_read(fd, 0, &ehdr, sizeof(ehdr))) goto out; + +#define MAX_SECTION_HEEADER_NUM 64 + if (ehdr.e_shnum > MAX_SECTION_HEEADER_NUM) + { + goto out; + } + + ElfW(Shdr) shdr_tab[MAX_SECTION_HEEADER_NUM]; + + if (0 != elf_read(fd, ehdr.e_shoff, &shdr_tab, ehdr.e_shnum*sizeof(ElfW(Shdr)))) goto out; + + for (size_t i = 0; i < ehdr.e_shnum; i ++) + { + if (shdr_tab[i].sh_type != SHT_NOTE) continue; + if (shdr_tab[i].sh_size < sizeof(ElfW(Nhdr))) continue; + + ElfW(Off) name_off = shdr_tab[ehdr.e_shstrndx].sh_offset + shdr_tab[i].sh_name; + + char name_buf[sizeof(".note.gnu.build-id")]; + + if (0 != elf_read(fd, name_off, name_buf, sizeof(name_buf))) goto out; + + if (0 != strcmp(name_buf, ".note.gnu.build-id")) continue; + + ElfW(Nhdr) nhdr; + + if (0 != elf_read(fd, shdr_tab[i].sh_offset, &nhdr, sizeof(nhdr))) goto out; + + if (0 == nhdr.n_descsz || nhdr.n_descsz > shdr_tab[i].sh_size) continue; + + if (nhdr.n_descsz > MAX_BUILD_ID_NUM) continue; + + build_id = (char *)malloc(nhdr.n_descsz*2 + 1); + + ElfW(Off) id_offset = shdr_tab[i].sh_offset + sizeof(nhdr) + NOTE_ALIGN(nhdr.n_namesz); + + char id_buf[MAX_BUILD_ID_NUM]; + + if (0 != elf_read(fd, id_offset, id_buf, nhdr.n_descsz)) goto out; + + for (i = 0; i < nhdr.n_descsz; i++) + { + sprintf(build_id + 2 * i, "%02hhx", id_buf[i]); + } + build_id[2 * nhdr.n_descsz] = 0; + break; + } + out: + if (fd > 0) close(fd); + + return build_id; +} + +__attribute__ ((visibility ("default"))) +char* npth_dlbuildid(const char* path) +{ + if (path == NULL) return NULL; + + char* build_id = get_buildid_from_memory(path); + + if (build_id == NULL) build_id = get_buildid_from_file(path); + + return build_id; +} + +#ifndef __LP64__ + +typedef struct link_map link_map_t; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" + +typedef struct ksoinfo { + char name[128]; + const void* phdr; + size_t phnum; + void* entry; + void* base; + unsigned size; + + uint32_t unused1; + void* dynamic; + + uint32_t unused2; + uint32_t unused3; + + struct ksoinfo* next; + unsigned flags; + + const char* strtab; + void* symtab; + + size_t nbucket; + size_t nchain; + unsigned* bucket; + unsigned* chain; + + unsigned* plt_got; + void* plt_rel; + size_t plt_rel_count; + + void* rel; + size_t rel_count; + + void* preinit_array; + size_t preinit_array_count; + + void* init_array; + size_t init_array_count; + void* fini_array; + size_t fini_array_count; + + void* init_func; + void* fini_func; + + unsigned* ARM_exidx; + size_t ARM_exidx_count; + + size_t ref_count; + link_map_t link_map; + + char constructors_called; + + void* load_bias; + + char has_text_relocations; + char has_DT_SYMBOLIC; +} ksoinfo_t; +#pragma clang diagnostic pop + +#define is_dl_info(_si) ((_si)->flags == 1 && (_si)->strtab != 0 && (_si)->symtab != 0 \ +&& (_si)->nbucket == 1 && (_si)->nchain == 8 && (_si)->bucket != 0 && (_si)->chain != 0 \ +&& (_si)->constructors_called == 0 && (_si)->has_text_relocations == 0 \ +&& (_si)->has_DT_SYMBOLIC == 1 && (_si)->load_bias == 0) + +static int k_dl_iterate_phdr(int (*cb)(struct dl_phdr_info *info, size_t size, void *data), void *data) +{ + ksoinfo_t* solist = dlopen(NULL, RTLD_LOCAL); + + if (solist == NULL) return 1; + + int rv = 0; + for (ksoinfo_t* si = solist; si != NULL; si = si->next) { + struct dl_phdr_info dl_info; + dl_info.dlpi_addr = si->link_map.l_addr; + dl_info.dlpi_name = si->link_map.l_name; + dl_info.dlpi_phdr = si->phdr; + dl_info.dlpi_phnum = (ElfW(Half))si->phnum; + rv = cb(&dl_info, sizeof(struct dl_phdr_info), data); + if (rv != 0) { + break; + } + } + + dlclose(solist); + + return rv; +} + +#endif + +__attribute__ ((visibility ("default"))) +void* npth_dliterater(void) +{ + void* handle = dlopen("libdl.so", RTLD_NOW); + + if (NULL == handle) return NULL; + + void *iterate = dlsym(handle, "dl_iterate_phdr"); + +#ifndef __LP64__ + if (iterate == NULL /* && api_level < 21 */) + { + struct ksoinfo* si = (struct ksoinfo *)handle; + if(is_dl_info(si)) + { + return (void*)&k_dl_iterate_phdr; + } + } +#endif + dlclose(handle); + + return iterate; +} + +#pragma clang diagnostic pop + +char* get_routine_so_path(size_t routine, size_t* start, size_t* end) { + char *path = NULL; + char line[1024] = {0}; + char seg_addr[64] = {0}; + FILE *f = fopen("/proc/self/maps", "r"); + if (f) { + while (fgets(line, sizeof(line), f)) { + if (1 == sscanf(line, "%[^ ]", seg_addr)) { + char* seperator = strchr(seg_addr, '-'); + if (seperator) { + *seperator = 0; + } else { + continue; + } + *start = strtoll(seg_addr, NULL, 16); + *end = strtoll(seperator+1, NULL, 16); + if (routine >= *start && routine <= *end) { + path = strchr(line, '/'); + if (path) { + char* last = strrchr(path, ' '); + if (NULL == last) { + last = strrchr(path, '\n'); + } + if (last) { + *last = '\0'; + } + } + break; + } else { + *start = 0; + *end = 0; + } + } + } + fclose(f); + } + if (path) { + return strdup(path); + } else { + return NULL; + } +} diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/npth_dl.h b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/npth_dl.h new file mode 100644 index 0000000..baea8ec --- /dev/null +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/npth_dl.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 ByteDance Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by puyingmin on 2019/12/18. +// + +#ifndef NPTH_DL_H +#define NPTH_DL_H + +#ifdef __cplusplus +extern "C" { +#endif +extern void* npth_dlopen(const char* so_name); +extern void* npth_dlsym(void* handle, const char* sym_str); +extern void* npth_dlopen_full(const char* so_name); +extern void* npth_dlsym_full(void* handle, const char* sym_str); +extern void* npth_dlsym_full_with_size(void* handle, const char* sym_str, size_t* sym_size_ptr); +extern void npth_dlclose(void* handle); +extern void* npth_dliterater(void); +extern char* npth_dlbuildid(const char*); + +extern char* get_routine_so_path(size_t routine, size_t* start, size_t* end); +extern char* get_path_from_maps(const void* address); + +#ifdef __cplusplus +} +#endif + +#endif //NPTH_DL_H \ No newline at end of file diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/scoped_dlopen.h b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/scoped_dlopen.h index aef18cd..f83e328 100644 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/scoped_dlopen.h +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/cpp/utils/scoped_dlopen.h @@ -15,14 +15,15 @@ */ #pragma once -#include + +#include "npth_dl.h" namespace rheatrace { class ScopedDlopen { public: ScopedDlopen(const char *so_name) { - handler = shadowhook_dlopen(so_name); + handler = npth_dlopen(so_name); } ~ScopedDlopen() { @@ -31,7 +32,7 @@ class ScopedDlopen { void release() { if (handler) { - shadowhook_dlclose(handler); + npth_dlclose(handler); } handler = nullptr; } From 61a482f79b77f9793497d430f1d65c7422c1e7dd Mon Sep 17 00:00:00 2001 From: linyilei <1029437677@qq.com> Date: Thu, 9 Apr 2026 10:50:22 +0800 Subject: [PATCH 6/6] Catch Throwable during Android trace init --- .../java/com/bytedance/rheatrace/trace/base/TraceGlobal.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/btrace-android/rhea-library/rhea-inhouse/src/main/java/com/bytedance/rheatrace/trace/base/TraceGlobal.java b/btrace-android/rhea-library/rhea-inhouse/src/main/java/com/bytedance/rheatrace/trace/base/TraceGlobal.java index 596bf9f..9c55970 100644 --- a/btrace-android/rhea-library/rhea-inhouse/src/main/java/com/bytedance/rheatrace/trace/base/TraceGlobal.java +++ b/btrace-android/rhea-library/rhea-inhouse/src/main/java/com/bytedance/rheatrace/trace/base/TraceGlobal.java @@ -32,7 +32,7 @@ public class TraceGlobal { nativeInit(Looper.getMainLooper().getThread()); JNIHook.init(); success = true; - } catch (Exception e) { + } catch (Throwable e) { Log.e(TAG, "rhea-trace init failed: ", e); success = false; } @@ -51,4 +51,4 @@ public static void capture(boolean force) { } private static native void nativeCapture(boolean force); -} \ No newline at end of file +}