-
-
Save SeeFlowerX/148336c839b183e70e287c66dac482d4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import logging | |
from pathlib import Path | |
from datetime import datetime | |
from bcc import BPF | |
GLOBAL_LOGGERS = {} | |
logger = None # type: logging.Logger | |
def setup_logger(log_tag: str, log_path: Path, first_call: bool = False) -> logging.Logger: | |
''' | |
输出的信息太多 Terminal可能不全 记录到日志文件 | |
''' | |
logger = GLOBAL_LOGGERS.get(log_tag) | |
if logger: | |
return logger | |
logger = logging.getLogger(log_tag) | |
GLOBAL_LOGGERS[log_tag] = logger | |
# 避免重新载入脚本时重复输出 | |
if first_call and logger.hasHandlers(): | |
logger.handlers.clear() | |
# 设置所有 handler 的日志等级 | |
logger.setLevel(logging.DEBUG) | |
# 添加终端 handler 只打印原始信息 | |
formatter = logging.Formatter('%(message)s') | |
ch = logging.StreamHandler() | |
ch.setLevel(logging.DEBUG) | |
ch.setFormatter(formatter) | |
logger.addHandler(ch) | |
# 添加文件 handler 记录详细时间和内容 | |
formatter = logging.Formatter('%(asctime)s.%(msecs)03d %(levelname)s: %(message)s', datefmt='%H:%M:%S') | |
fh = logging.FileHandler(log_path.as_posix(), encoding='utf-8', delay=True) | |
fh.setLevel(logging.DEBUG) | |
fh.setFormatter(formatter) | |
logger.addHandler(fh) | |
return logger | |
BPF_CODE_include = """ | |
#include <linux/ptrace.h> | |
""" | |
BPF_CODE_open = """ | |
struct ArtMethod { | |
u32 declaring_class_; | |
u32 access_flags_; | |
//u32 dex_code_item_offset_; | |
u32 dex_method_index_; | |
u16 method_index_; | |
}; | |
struct MirrorClass { | |
u32 class_loader_; | |
u32 pad0; | |
u32 component_type_; | |
u32 pad1; | |
u32 dex_cache_; | |
u32 pad2; | |
u64 ext_data_; | |
}; | |
struct DexCache { | |
u32 class_loader_; | |
u32 pad0; | |
u32 location_; | |
u32 pad1; | |
void* dex_file_; | |
u64 preresolved_strings_; | |
}; | |
struct DexFile { | |
void* vptr; | |
uint8_t* begin_; | |
size_t size_; | |
uint8_t* data_begin_; | |
size_t data_size_; | |
char location_[24]; | |
uint32_t location_checksum_; | |
void* header_; | |
void* string_ids_; | |
void* type_ids_; | |
void* field_ids_; | |
void* method_ids_; | |
void* proto_ids_; | |
}; | |
struct MethodId { | |
uint16_t class_idx_; | |
uint16_t proto_idx_; | |
uint32_t name_idx_; | |
}; | |
struct StringId { | |
uint32_t string_data_off_; | |
}; | |
struct TypeId { | |
uint32_t descriptor_idx_; | |
}; | |
struct FieldId { | |
uint16_t class_idx_; | |
uint16_t type_idx_; | |
uint32_t name_idx_; | |
}; | |
struct ProtoId { | |
uint32_t shorty_idx_; | |
uint16_t return_type_idx_; | |
uint16_t pad_; | |
uint32_t parameters_off_; | |
}; | |
struct TypeItem { | |
uint16_t type_idx_; | |
}; | |
struct DataEvent { | |
u32 pid; | |
u32 tid; | |
u64 art_method; | |
u32 dex_method_index; | |
u16 method_index; | |
u32 method_id_size2; | |
u32 method_id_size3; | |
char class_name[128]; | |
char ret_sig[128]; | |
char method_name[128]; | |
u32 param_size; | |
char param_sig1[128]; | |
char param_sig2[128]; | |
char param_sig3[128]; | |
char param_sig4[128]; | |
char param_sig5[128]; | |
char param_sig6[128]; | |
}; | |
struct JniDataEvent { | |
u64 env; | |
u64 obj; | |
u64 art_method; | |
u32 dex_method_index; | |
u16 method_index; | |
u32 method_id_size2; | |
u32 method_id_size3; | |
u32 param_size; | |
}; | |
BPF_PERCPU_ARRAY(jni_perf_data, struct DataEvent, 1); | |
BPF_PERF_OUTPUT(perf_jni); | |
int probe_hook_jni_invoke_enter(struct pt_regs *ctx) { | |
u64 pid_tgid = bpf_get_current_pid_tgid(); | |
u32 pid = pid_tgid >> 32; | |
u32 tid = pid_tgid & 0xffffffff; | |
u32 uid = bpf_get_current_uid_gid(); | |
PID_FILTER | |
TID_FILTER | |
UID_FILTER | |
struct JniDataEvent jni_data_event_t; | |
//__builtin_memset(&jni_data_event_t, 0, sizeof(jni_data_event_t)); | |
jni_data_event_t.art_method = PT_REGS_PARM3(ctx); | |
struct ArtMethod art_method; | |
bpf_probe_read_user(&art_method, sizeof(art_method), (void *)jni_data_event_t.art_method); | |
jni_data_event_t.dex_method_index = art_method.dex_method_index_; | |
jni_data_event_t.method_index = art_method.method_index_; | |
struct MirrorClass mirror_class; | |
bpf_probe_read_user(&mirror_class, sizeof(mirror_class), art_method.declaring_class_); | |
struct DexCache dex_cache; | |
bpf_probe_read_user(&dex_cache, sizeof(dex_cache), mirror_class.dex_cache_); | |
struct DexFile dex_file; | |
bpf_probe_read_user(&dex_file, sizeof(dex_file), dex_cache.dex_file_); | |
struct TypeId type_id; | |
struct StringId string_id; | |
struct MethodId method_id; | |
bpf_probe_read_user(&method_id, sizeof(method_id), dex_file.method_ids_ + (sizeof(method_id) * art_method.dex_method_index_)); | |
uint32_t parameters_offset; | |
bpf_probe_read_user(¶meters_offset, sizeof(parameters_offset), dex_file.proto_ids_ + (12 * method_id.proto_idx_ + 8)); | |
jni_data_event_t.method_id_size2 = parameters_offset; | |
//bpf_trace_printk("[jni_invoke] parameters_offset_0 before:%d after:%d\\n", jni_data_event_t.method_id_size2, parameters_offset); | |
void* param_sig_ptr[6]; | |
jni_data_event_t.param_size = 0; | |
if (parameters_offset > 0) { | |
jni_data_event_t.method_id_size3 = parameters_offset; | |
//bpf_trace_printk("[jni_invoke] parameters_offset before:%d after:%d\\n", jni_data_event_t.method_id_size2, jni_data_event_t.method_id_size3); | |
uint32_t param_size; | |
bpf_probe_read_user(¶m_size, sizeof(param_size), dex_file.data_begin_ + parameters_offset); | |
if (param_size > 0 && param_size <= 6) { | |
struct TypeItem type_item; | |
struct TypeId param_type_id; | |
jni_data_event_t.param_size = param_size; | |
//uint16_t param_type_idx; | |
//bpf_probe_read_user(¶m_type_idx, sizeof(param_type_idx), dex_file.data_begin_ + parameters_offset + 4); | |
#pragma unroll | |
for (uint32_t i = 0; i < 6; i++) { | |
if (i == param_size) break; | |
bpf_probe_read_user(&type_item, sizeof(type_item), dex_file.data_begin_ + parameters_offset + 4 + sizeof(type_item) * i); | |
bpf_probe_read_user(¶m_type_id, sizeof(param_type_id), dex_file.type_ids_ + (sizeof(param_type_id) * type_item.type_idx_)); | |
bpf_probe_read_user(&string_id, sizeof(string_id), dex_file.string_ids_ + (sizeof(string_id) * param_type_id.descriptor_idx_)); | |
param_sig_ptr[i] = dex_file.data_begin_ + string_id.string_data_off_; | |
} | |
} | |
} | |
struct ProtoId proto_id; | |
bpf_probe_read_user(&proto_id, sizeof(proto_id), dex_file.proto_ids_ + (sizeof(proto_id) * method_id.proto_idx_)); | |
bpf_probe_read_user(&type_id, sizeof(type_id), dex_file.type_ids_ + (sizeof(type_id) * proto_id.return_type_idx_)); | |
bpf_probe_read_user(&string_id, sizeof(string_id), dex_file.string_ids_ + (sizeof(string_id) * type_id.descriptor_idx_)); | |
void *ret_sig_ptr = dex_file.data_begin_ + string_id.string_data_off_; | |
bpf_probe_read_user(&type_id, sizeof(type_id), dex_file.type_ids_ + (sizeof(type_id) * method_id.class_idx_)); | |
bpf_probe_read_user(&string_id, sizeof(string_id), dex_file.string_ids_ + (sizeof(string_id) * type_id.descriptor_idx_)); | |
void *class_name_ptr = dex_file.data_begin_ + string_id.string_data_off_; | |
bpf_probe_read_user(&string_id, sizeof(string_id), dex_file.string_ids_ + (sizeof(string_id) * method_id.name_idx_)); | |
void *method_name_ptr = dex_file.data_begin_ + string_id.string_data_off_; | |
u32 zero = 0; | |
struct DataEvent* data = jni_perf_data.lookup(&zero); | |
if (data == NULL) return 0; | |
data->pid = pid; | |
data->tid = tid; | |
data->art_method = jni_data_event_t.art_method; | |
data->dex_method_index = jni_data_event_t.dex_method_index; | |
data->method_index = jni_data_event_t.method_index; | |
data->method_id_size2 = jni_data_event_t.method_id_size2; | |
data->method_id_size3 = jni_data_event_t.method_id_size3; | |
bpf_probe_read_user(data->ret_sig, sizeof(data->ret_sig), ret_sig_ptr); | |
bpf_probe_read_user(data->class_name, sizeof(data->class_name), class_name_ptr); | |
bpf_probe_read_user(data->method_name, sizeof(data->method_name), method_name_ptr); | |
data->param_size = jni_data_event_t.param_size; | |
//if (jni_data_event_t.param_size > 0) { | |
// bpf_probe_read_user(data->param_sig1, sizeof(data->param_sig1), (void*)param_sig_ptr[0]); | |
//} | |
//if (jni_data_event_t.param_size > 1) { | |
// bpf_probe_read_user(data->param_sig2, sizeof(data->param_sig2), (void*)param_sig_ptr[1]); | |
//} | |
//if (jni_data_event_t.param_size > 2) { | |
// bpf_probe_read_user(data->param_sig3, sizeof(data->param_sig3), (void*)param_sig_ptr[2]); | |
//} | |
//if (jni_data_event_t.param_size > 3) { | |
// bpf_probe_read_user(data->param_sig4, sizeof(data->param_sig4), (void*)param_sig_ptr[3]); | |
//} | |
//if (jni_data_event_t.param_size > 4) { | |
// bpf_probe_read_user(data->param_sig5, sizeof(data->param_sig5), (void*)param_sig_ptr[4]); | |
//} | |
//if (jni_data_event_t.param_size > 5) { | |
// bpf_probe_read_user(data->param_sig6, sizeof(data->param_sig6), (void*)param_sig_ptr[5]); | |
//} | |
perf_jni.perf_submit(ctx, data, sizeof(struct DataEvent)); | |
return 0; | |
} | |
""" | |
def get_str(data: bytes, decode: bool = True): | |
if len(data) == 0: | |
return | |
mask = 0x80000000 | |
if data[0] & mask != 0: | |
if data[1] & mask != 0: | |
if data[2] & mask != 0: | |
offset = 4 | |
if data[3] < 0: | |
offset = 5 | |
else: | |
offset = 3 | |
else: | |
offset = 2 | |
else: | |
offset = 1 | |
if decode: | |
text = data[offset:].decode('utf-8') | |
return text.replace("/", ".").lstrip("L").rstrip(";") | |
else: | |
return data[offset:] | |
class BPFHooker: | |
def __init__(self, library: str, uid: int, pid: int = -1, tid: int = -1) -> None: | |
self.uid = uid | |
self.pid = pid | |
self.tid = tid | |
self.library = library | |
self.bpf_module = None # type: BPF | |
def hook(self): | |
text = BPF_CODE_include | |
text += BPF_CODE_open | |
if self.pid > 0: | |
text = text.replace('PID_FILTER', f'if (pid != {self.pid}) {{ return 0; }}') | |
else: | |
text = text.replace('PID_FILTER', '') | |
if self.tid > 0: | |
text = text.replace('TID_FILTER', f'if (tid != {self.tid}) {{ return 0; }}') | |
else: | |
text = text.replace('TID_FILTER', '') | |
if self.uid > 0: | |
text = text.replace('UID_FILTER', f'if (uid != {self.uid}) {{ return 0; }}') | |
else: | |
text = text.replace('UID_FILTER', '') | |
self.bpf_module = BPF(text=text) | |
sym_InvokeVirtualOrInterfaceWithVarArgs_ArtMethod = '_ZN3art35InvokeVirtualOrInterfaceWithVarArgsIPNS_9ArtMethodEEENS_6JValueERKNS_33ScopedObjectAccessAlreadyRunnableEP8_jobjectT_St9__va_list' | |
self.bpf_module.attach_uprobe(name=self.library, sym=sym_InvokeVirtualOrInterfaceWithVarArgs_ArtMethod, fn_name='probe_hook_jni_invoke_enter', pid=self.pid) | |
logger.info('attach end') | |
def print_event_perf_jni(self, ctx, data, size): | |
event = self.bpf_module['perf_jni'].event(data) | |
logger.info( | |
f'[env->CallObjectMethod] tid={event.tid} ' | |
f'method={event.art_method:#12x} ' | |
f'{get_str(event.ret_sig)} {get_str(event.class_name)}->{get_str(event.method_name)}(...) ' | |
f'{event.param_size}' | |
) | |
def show(self): | |
self.bpf_module["perf_jni"].open_perf_buffer(self.print_event_perf_jni) | |
while 1: | |
try: | |
self.bpf_module.perf_buffer_poll() | |
except KeyboardInterrupt: | |
exit() | |
def main(): | |
global logger | |
log_tag = 'jnitrace' | |
log_time = datetime.now().strftime('%Y%m%d_%H%M%S') | |
log_path = Path(__file__).parent / f'logs/{log_tag}_{log_time}.log' | |
logger = setup_logger(log_tag, log_path, first_call=True) | |
# uid = 10240 | |
uid = 10235 | |
uid = 10236 | |
library = "/apex/com.android.art/lib64/libart.so" | |
bpf_hooker = BPFHooker(library, uid) | |
bpf_hooker.hook() | |
bpf_hooker.show() | |
# /sys/kernel/debug 是需要挂载 debugfs 才有 | |
# echo 1 > /sys/kernel/tracing/tracing_on | |
# cat /sys/kernel/tracing/trace_pipe | |
# echo 0 > /sys/kernel/tracing/tracing_on | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment