腾讯bugly怎么用 支持海外游戏吗

【腾讯Bugly干货分享】Android Linker 与 SO 加壳技术
编辑:www.fx114.net
本篇文章主要介绍了"【腾讯Bugly干货分享】Android Linker 与 SO 加壳技术 ",主要涉及到【腾讯Bugly干货分享】Android Linker 与 SO 加壳技术 方面的内容,对于【腾讯Bugly干货分享】Android Linker 与 SO 加壳技术 感兴趣的同学可以参考一下。
本文来自于,非经作者同意,请勿转载,原文地址:
作者:王赛
Android 系统安全愈发重要,像传统pc安全的可执行文件加固一样,应用加固是Android系统安全中非常重要的一环。目前Android 应用加固可以分为dex加固和Native加固,Native 加固的保护对象为 Native 层的 SO 文件,使用加壳、反调试、混淆、VM 等手段增加SO文件的反编译难度。目前最主流的 SO 文件保护方案还是加壳技术, 在SO文件加壳和脱壳的攻防技术领域,最重要的基础的便是对于 Linker 即装载链接机制的理解。对于非安全方向开发者,深刻理解系统的装载与链接机制也是进阶的必要条件。
本文详细分析了 Linker 对 SO 文件的装载和链接过程,最后对 SO 加壳的关键技术进行了简要的介绍。
对于 Linker 的学习,还应该包括 Linker 自举、可执行文件的加载等技术,但是限于本人的技术水平,本文的讨论范围限定在 SO 文件的加载,也就是在调用dlopen("libxx.SO")之后,Linker 的处理过程。
本文基于 Android 5.0 AOSP 源码,仅针对 ARM 平台,为了增强可读性,文中列举的源码均经过删减,去除了其他 CPU 架构的相关源码以及错误处理。
P.S. :阅读本文的读者需要对 ELF 文件结构有一定的了解。
2. SO 的装载与链接
2.1 整体流程说明
1. do_dlopen 调用 dl_open 后,中间经过 dlopen_ext, 到达第一个主要函数 do_dlopen:
soinfo* do_dlopen(const char* name, int flags, const Android_dlextinfo* extinfo) {
protect_data(PROT_READ | PROT_WRITE);
soinfo* si = find_library(name, flags, extinfo); // 查找 SO
if (si != NULL) {
si-&CallConstructors(); // 调用 SO 的 init 函数
protect_data(PROT_READ);
do_dlopen 调用了两个重要的函数,第一个是find_library, 第二个是 soinfo 的成员函数 CallConstructors,find_library 函数是 SO 装载链接的后续函数, 完成 SO 的装载链接后, 通过 CallConstructors 调用 SO 的初始化函数。
2. find_library_internal find_library 直接调用了 find_library_internal,下面直接看 find_library_internal函数:
static soinfo* find_library_internal(const char* name, int dlflags, const Android_dlextinfo* extinfo) {
if (name == NULL) {
soinfo* si = find_loaded_library_by_name(name);
// 判断 SO 是否已经加载
if (si == NULL) {
TRACE("[ '%s' has not been found by name.
Trying harder...]", name);
si = load_library(name, dlflags, extinfo);
// 继续 SO 的加载流程
if (si != NULL && (si-&flags & FLAG_LINKED) == 0) {
DL_ERR("recursive link to \"%s\"", si-&name);
return NULL;
find_library_internal 首先通过 find_loaded_library_by_name 函数判断目标 SO 是否已经加载,如果已经加载则直接返回对应的soinfo指针,没有加载的话则调用 load_library 继续加载流程,下面看 load_library 函数。
3. load_library
static soinfo* load_library(const char* name, int dlflags, const Android_dlextinfo* extinfo) {
int fd = -1;
// Open the file.
fd = open_library(name);
// 打开 SO 文件,获得文件描述符 fd
ElfReader elf_reader(name, fd);
// 创建 ElfReader 对象
// Read the ELF header and load the segments.
if (!elf_reader.Load(extinfo)) {
// 使用 ElfReader 的 Load 方法,完成 SO 装载
return NULL;
soinfo* si = soinfo_alloc(SEARCH_NAME(name), &file_stat);
// 为 SO 分配新的 soinfo 结构
if (si == NULL) {
return NULL;
si-&base = elf_reader.load_start();
// 根据装载结果,更新 soinfo 的成员变量
si-&size = elf_reader.load_size();
si-&load_bias = elf_reader.load_bias();
si-&phnum = elf_reader.phdr_count();
si-&phdr = elf_reader.loaded_phdr();
if (!soinfo_link_image(si, extinfo)) {
// 调用 soinfo_link_image 完成 SO 的链接过程
soinfo_free(si);
return NULL;
load_library 函数呈现了 SO 装载链接的整个流程,主要有3步:
装载:创建ElfReader对象,通过 ElfReader 对象的 Load 方法将 SO 文件装载到内存
分配soinfo:调用 soinfo_alloc 函数为 SO 分配新的 soinfo 结构,并按照装载结果更新相应的成员变量
链接: 调用 soinfo_link_image 完成 SO 的链接
通过前面的分析,可以看到, load_library 函数中包含了 SO 装载链接的主要过程, 后文主要通过分析 ElfReader 类和 soinfo_link_image 函数, 来分别介绍 SO 的装载和链接过程。
2.2 装载
在 load_library 中, 首先初始化 elf_reader 对象, 第一个参数为 SO 的名字, 第二个参数为文件描述符 fd: ElfReader elf_reader(name, fd) 之后调用 ElfReader 的 load 方法装载 SO。
// Read the ELF header and load the segments.
if (!elf_reader.Load(extinfo)) {
return NULL;
ElfReader::Load 方法如下:
bool ElfReader::Load(const Android_dlextinfo* extinfo) {
return ReadElfHeader() &&
// 读取 elf header
VerifyElfHeader() &&
// 验证 elf header
ReadProgramHeader() &&
// 读取 program header
ReserveAddressSpace(extinfo) &&// 分配空间
LoadSegments() &&
// 按照 program header 指示装载 segments
FindPhdr();
// 找到装载后的 phdr 地址
ElfReader::Load 方法首先读取 SO 的elf header,再对elf header进行验证,之后读取program header,根据program header 计算 SO 需要的内存大小并分配相应的空间,紧接着将 SO 按照以 segment 为单位装载到内存,最后在装载到内存的 SO 中找到program header,方便之后的链接过程使用。 下面深入 ElfReader 的这几个成员函数进行详细介绍。
bool ElfReader::ReadElfHeader() {
ssize_t rc = read(fd_, &header_, sizeof(header_));
if (rc != sizeof(header_)) {
ReadElfHeader 使用 read 直接从 SO 文件中将 elfheader 读取 header 中,header_ 为 ElfReader 的成员变量,类型为 Elf32_Ehdr,通过 header 可以方便的访问 elf header中各个字段,elf header中包含有 program header table、section header table等重要信息。 对 elf header 的验证包括:
magic字节
32/64 bit 与当前平台是否一致
大小端
类型:可执行文件、SO &
版本:一般为 1,表示当前版本
平台:ARM、x86、amd64 &
有任何错误都会导致加载失败。
bool ElfReader::ReadProgramHeader() {
phdr_num_ = header_.e_
// program header 数量
// mmap 要求页对齐
ElfW(Addr) page_min = PAGE_START(header_.e_phoff);
ElfW(Addr) page_max = PAGE_END(header_.e_phoff + (phdr_num_ * sizeof(ElfW(Phdr))));
ElfW(Addr) page_offset = PAGE_OFFSET(header_.e_phoff);
phdr_size_ = page_max - page_
// 使用 mmap 将 program header 映射到内存
void* mmap_result = mmap(NULL, phdr_size_, PROT_READ, MAP_PRIVATE, fd_, page_min);
phdr_mmap_ = mmap_
// ElfReader 的成员变量 phdr_table_ 指向program header table
phdr_table_ = reinterpret_cast&ElfW(Phdr)*&(reinterpret_cast&char*&(mmap_result) + page_offset);
将 program header 在内存中单独映射一份,用于解析program header 时临时使用,在 SO 装载到内存后,便会释放这块内存,转而使用装载后的 SO 中的program header。
2.2.3 reserve space & 计算 load size
bool ElfReader::ReserveAddressSpace(const Android_dlextinfo* extinfo) {
ElfW(Addr) min_
// 计算 加载SO 需要的空间大小
load_size_ = phdr_table_get_load_size(phdr_table_, phdr_num_, &min_vaddr);
// min_vaddr 一般情况为零,如果不是则表明 SO 指定了加载基址
uint8_t* addr = reinterpret_cast&uint8_t*&(min_vaddr);
int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS;
start = mmap(addr, load_size_, PROT_NONE, mmap_flags, -1, 0);
load_start_ =
load_bias_ = reinterpret_cast&uint8_t*&(start) -
首先调用 phdr_table_get_load_size 函数获取 SO 在内存中需要的空间load_size,然后使用 mmap 匿名映射,预留出相应的空间。
关于loadbias: SO 可以指定加载基址,但是 SO 指定的加载基址可能不是页对齐的,这种情况会导致实际映射地址和指定的加载地址有一个偏差,这个偏差便是 load_bias_,之后在针对虚拟地址进行计算时需要使用 load_bias_ 修正。普通的 SO 都不会指定加载基址,这时min_vaddr = 0,则 load_bias_ = load_start_,即load_bias_ 等于加载基址,下文会将 load_bias_ 直接称为基址。
下面深入phdr_table_get_load_size分析一下 load_size 的计算:使用成员变量 phdr_table 遍历所有的program header, 找到所有类型为 PT_LOAD 的 segment 的 p_vaddr 的最小值,p_vaddr + p_memsz 的最大值,分别作为 min_vaddr 和 max_vaddr,在将两个值分别对齐到页首和页尾,最终使用对齐后的 max_vaddr - min_vaddr 得到 load_size。
size_t phdr_table_get_load_size(const ElfW(Phdr)* phdr_table, size_t phdr_count,
ElfW(Addr)* out_min_vaddr,
ElfW(Addr)* out_max_vaddr) {
ElfW(Addr) min_vaddr = UINTPTR_MAX;
ElfW(Addr) max_vaddr = 0;
bool found_pt_load =
for (size_t i = 0; i & phdr_ ++i) {
// 遍历 program header
const ElfW(Phdr)* phdr = &phdr_table[i];
if (phdr-&p_type != PT_LOAD) {
found_pt_load =
if (phdr-&p_vaddr & min_vaddr) {
min_vaddr = phdr-&p_
// 记录最小的虚拟地址
if (phdr-&p_vaddr + phdr-&p_memsz & max_vaddr) {
max_vaddr = phdr-&p_vaddr + phdr-&p_
// 记录最大的虚拟地址
if (!found_pt_load) {
min_vaddr = 0;
min_vaddr = PAGE_START(min_vaddr);
max_vaddr = PAGE_END(max_vaddr);
if (out_min_vaddr != NULL) {
*out_min_vaddr = min_
if (out_max_vaddr != NULL) {
*out_max_vaddr = max_
return max_vaddr - min_
// load_size = max_vaddr - min_vaddr
2.2.4 Load Segments
遍历 program header table,找到类型为 PT_LOAD 的 segment:
计算 segment 在内存空间中的起始地址 segstart 和结束地址 seg_end,seg_start 等于虚拟偏移加上基址load_bias,同时由于 mmap 的要求,都要对齐到页边界得到 seg_page_start 和 seg_page_end。
计算 segment 在文件中的页对齐后的起始地址 file_page_start 和长度 file_length。
使用 mmap 将 segment 映射到内存,指定映射地址为 seg_page_start,长度为 file_length,文件偏移为 file_page_start。
bool ElfReader::LoadSegments() {
for (size_t i = 0; i & phdr_num_; ++i) {
const ElfW(Phdr)* phdr = &phdr_table_[i];
if (phdr-&p_type != PT_LOAD) {
// Segment 在内存中的地址.
ElfW(Addr) seg_start = phdr-&p_vaddr + load_bias_;
ElfW(Addr) seg_end
= seg_start + phdr-&p_
ElfW(Addr) seg_page_start = PAGE_START(seg_start);
ElfW(Addr) seg_page_end
= PAGE_END(seg_end);
ElfW(Addr) seg_file_end
= seg_start + phdr-&p_
// 文件偏移
ElfW(Addr) file_start = phdr-&p_
ElfW(Addr) file_end
= file_start + phdr-&p_
ElfW(Addr) file_page_start = PAGE_START(file_start);
ElfW(Addr) file_length = file_end - file_page_
if (file_length != 0) {
// 将文件中的 segment 映射到内存
void* seg_addr = mmap(reinterpret_cast&void*&(seg_page_start),
file_length,
PFLAGS_TO_PROT(phdr-&p_flags),
MAP_FIXED|MAP_PRIVATE,
file_page_start);
// 如果 segment 可写, 并且没有在页边界结束,那么就将 segemnt end 到页边界的内存清零。
if ((phdr-&p_flags & PF_W) != 0 && PAGE_OFFSET(seg_file_end) & 0) {
memset(reinterpret_cast&void*&(seg_file_end), 0, PAGE_SIZE - PAGE_OFFSET(seg_file_end));
seg_file_end = PAGE_END(seg_file_end);
// 将 (内存长度 - 文件长度) 对应的内存进行匿名映射
if (seg_page_end & seg_file_end) {
void* zeromap = mmap(reinterpret_cast&void*&(seg_file_end),
seg_page_end - seg_file_end,
PFLAGS_TO_PROT(phdr-&p_flags),
MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE,
load_library 在调用 load_segments 完成装载后,接着调用 soinfo_alloc 函数为目标SO分配soinfo,soinfo_alloc 函数实现如下:
static soinfo* soinfo_alloc(const char* name, struct stat* file_stat) {
soinfo* si = g_soinfo_allocator.alloc();
//分配空间,可以简单理解为 malloc
// Initialize the new element.
memset(si, 0, sizeof(soinfo));
strlcpy(si-&name, name, sizeof(si-&name));
si-&flags = FLAG_NEW_SOINFO;
sonext-&next =
// 加入到存有所有 soinfo 的链表中
Linker 为 每个 SO 维护了一个soinfo结构,调用 dlopen时,返回的句柄其实就是一个指向该 SO 的 soinfo 指针。soinfo 保存了 SO 加载链接以及运行期间所需的各类信息,简单列举一下:
装载链接期间主要使用的成员:
const ElfW(Phdr)*
size_
ElfW(Addr)
size_
一、不得利用本站危害国家安全、泄露国家秘密,不得侵犯国家社会集体的和公民的合法权益,不得利用本站制作、复制和传播不法有害信息!
二、互相尊重,对自己的言论和行为负责。
本文标题:
本页链接:日 14:00 ~ 日 17:30
限额200人(已报名177)
活动已结束
付费活动,请选择票种
4777 人浏览
发现你感兴趣的活动,结交你聊得来的朋友!
微信“扫一扫”
优活动 更精彩!
ID:ihuodongxing
哎呀~该活动已结束
哎呀~主办方的活动已经结束啦,为您推荐更多活动
展开活动详情 ︾
活动票种...
活动内容...
在Swift之前,Objective-C是唯一的iOS编程语言。面世一年,Swift在多个编程语言排行榜均取得不俗的成绩。尽管目前OC运用更为广泛,但未来,迅猛发展的Swift是否会让OC退出人们的视线?
在Swift之前,80年代推出的Objective-C是唯一的iOS应用编程语言。但如今Objective-C已经有点过时了,开发者们甚至用“老掉牙"、"冗长乏味"、"令人生厌”来形容它。当然,只要是热门语言,总会引来褒贬不一的评价。
随着WWDC 2015的举行,Swift
2.0面世,不仅带来了更多的新特性,更被苹果寄予厚望,有可能代替Objective-C成为iOS平台的标准开发语言。那么Swift能否替代Objective-C成为新的王者?现有的项目是否需要迁移?我们是否应该马上开始学习Swift呢?在本次沙龙中,来自的腾讯的开发专家将从语法特性,学习成本,代码效率,生态环境4个方面入手,对Swift和其他现代语言进行分析、对比,共同探讨App开发趋势和职业发展前景。
时间:2015 年
7 月 11 日(周六)
14:00 — 18:00
地点:中关村创业大街
13:30 — 14:00
14:00 — 14:10
主持人开场 + 嘉宾介绍
14:10 — 15:30
如何在实际工程中使用Swift开发 / 陈曦: 腾讯自选股高级开发工程师
15:30 — 16:00
交流问答与茶歇
16:00 — 16:40
Swift是花拳绣腿吗?——谈谈开发语言与职业生涯的选择 / 任旻: 手机QQ高级工程师,腾讯学院优秀讲师
16:50 — 17:30
陈曦,北京邮电大学研究生毕业,腾讯OMG产经资讯部终端开发工程师。3年嵌入式设备开发经验,2年iOS终端开发经验,在Swift上有一定积累。获得腾讯最具潜力毕业生奖项,多次获得公司优秀员工。目前在腾讯自选股团队负责iOS客户端开发工作。
任旻,北京工业大学硕士,
2005年加入微软中国有限公司,2009年加入腾讯,现任高级工程师,曾负责开发“QQ概念版”、“Q+”、"QQ互联”、“iPad
QQ”等产品。腾讯学院讲师,专利达人。一直从事互联网领域软件开发和生态系统建设等工作。多次参加国内外互联网开发大会,曾多次在微软TechEd,Mix大会、业界Velocity,TopSummit,HTML5峰会以及多个开发者沙龙上进行过分享。
腾讯 Bugly
移动开发者沙龙 是腾讯为广大移动开发者搭建的分享平台,在这里我们将邀请腾讯内部的开发大咖与大家进行面对面的交流,以实例和模型全面系统地介绍先进开发技术的运用、优质资源与工具的使用等热点话题。同时,也欢迎有意向的资深开发者加入我们,为大家带来更多的干货分享。
分享到微信
活动标签...
最近参与...
您还可能感兴趣...
您有任何问题,在这里提问!
全部讨论...
活动主办方...
腾讯无线研发部主要负责腾讯内部移动项目的研发支撑,通过输出高效,稳定的自动化工具以及研发流程控制,帮助内部项目组快速,高效的完成项目开发工作。
微信扫一扫,分享才精彩
分享此活动到→
微信朋友圈!
活动行帐号登录
用合作网站帐号登录
活动行 v4.2.2 (C) huodongxing.com All Rights Reserved.
页面正在加载中,请耐心等待...腾讯bugly - 文章 - 伯乐在线
& Articles by: 腾讯bugly
2016年应该是直播元年,直播应用百团大战,QQ 空间也在6.5版本上线了直播功能,从无到有、快速搭建了直播间。“先扛住再优化”,第一个版本和竞品相比,我们进入直播间的速度比较慢。根据外网统计在6.5版本的用户端看到画面需要4.4s,因此在6.5发布之后,着手启动了优化工作,目标:观看直播需要达到秒进体验(1s内看到画面)。
关于伯乐在线博客
在这个信息爆炸的时代,人们已然被大量、快速并且简短的信息所包围。然而,我们相信:过多“快餐”式的阅读只会令人“虚胖”,缺乏实质的内涵。伯乐在线内容团队正试图以我们微薄的力量,把优秀的原创文章和译文分享给读者,为“快餐”添加一些“营养”元素。
新浪微博:
推荐微信号
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选文章
– UI,网页,交互和用户体验
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2018 伯乐在线和其他开发者一起互动,更多惊喜等着你!
当前位置:
企业(个人)名称:
平台模式:
平台支持:
授权方式:
开发语言:
更新时间:
Bugly是腾讯内部产品质量监控平台的外发版本,其主要功能是App发布以后,对用户侧发生的crash以及卡顿现象进行监控并上报,让开发同学可以第一时间了解到app的质量情况,及时机型修改。目前腾讯内部所有的产品,均在使用其进行线上产品的崩溃监控。
1、全平台支持:目前支持iOS和Android两大主流平台的崩溃分析上报,包括iOS的不同开发语言(OC以及Swift),&并支持Android操作系统的java层和NDK层全面的崩溃上报。同时,我们还对rm64-v8a、armeabi、armeabi-v7a、x86、x86_64等不同的架构进行了适配,不管用户的手机是什么架构,都可以完整的上报crash信息,并进行代码还原。2、游戏深度支持:对于使用Cocos以及U3D引擎进行开发的游戏,我们的代码还原可以帮助开发这定位到引擎脚本的堆栈,包括Cocos的lua脚本,C++脚本,以及U3D的C#脚本。3、实时、准确、完整的数据:依托腾讯强大的IDC,无论App的使用用户在国内还是海外,可以在用户发生Crash后,实时上报到系统平台,并提供完成的Crash堆栈数据和发生问题的机型详细数据:有哪些机型发生了这个问题,机型的sdk版本号分布,内存以及硬盘剩余占比,是否root以及发生问题时其他线程的日志和系统Logcat的日志信息。开发者可以实时查看应用的质量情况,尤其是在刚上线的前半个小时,快速了解应用质量。4、监控、统计功能:用户可以根据自身业务的需求设置告警阀值,当单位时间内Crash数量超过阀值后,用户可以第一时间收到微信告警。同时,日报以及周报会把影响用户数量最多的问题Highlight出来,方便开发者对主要的问题进行快速修改。5、智能合并分析:通过对腾讯现有产品的海量Crash数据进行分析,完成精准的合并算法,帮助开发者把同一根因引起的crash进行合并,开发同学不需要重复分析同一根因引起的Crash。Bugly经过腾讯内部4年打磨,目前腾讯所有产品都在使用,其适配性基本覆盖了中国市场的移动设备以及网络环境,可靠性有保证。使用Bugly,就等于您使用了和手机QQ、QQ空间、手机管家相同的质量保障途径,Bugly会持续对产品进行优化打磨,帮助更多的开发者打造更有品质的产品。
Copyright @ CTO.COM 京ICP证060544号 版权所有 未经许可 请勿转载腾讯Bugly的个人频道
上传背景图可更好的宣传品牌形象
支持jpg、jpeg、png格式,大小2M以内
尺寸不小于1240 x 360像素
高亮区域为手机显示效果拖动可改变背景选择区域
腾讯Bugly移动开发者沙龙视频及崩溃分析干货分享中心
腾讯Bugly移动开发者沙龙视频及崩溃分析干货分享中心
播放量 8.6万
微信分享给好友
微信分享给好友
最近打赏过小伙伴,就会出现在这里哦!
为了让开发者更直观的了解和使用Bugly热更新推出的这么一套课程。
修改资料:
一句话简介
还可以输入30个字
http://t.qq.com/
http://user.qzone.qq.com/
修改头像:}

我要回帖

更多关于 腾讯bugly接入 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信