当前位置: 首页 > news >正文

常用wap网站开发工具 手机网站制作软件免费建立网站论坛

常用wap网站开发工具 手机网站制作软件,免费建立网站论坛,seo入门讲解,WordPress登录ip作为移动开发者#xff0c;最头疼的莫过于遇到产品上线以后出现了Bug#xff0c;但是本地开发环境又无法复现的情况。常见的调查线上棘手问题方式大概如下#xff1a; 方法优点缺点联系用户安装已添加测试日志的APK方便定位问题需要用户积极配合#xff0c;如果日志添加不全… 作为移动开发者最头疼的莫过于遇到产品上线以后出现了Bug但是本地开发环境又无法复现的情况。常见的调查线上棘手问题方式大概如下 方法优点缺点联系用户安装已添加测试日志的APK方便定位问题需要用户积极配合如果日志添加不全面还需要反复重试提前在一些关键路径设置埋点在用户出现问题以后上报日志进而定位问题不需要用户深度配合关键路径不好预测以上两种方法在之前调查线上问题时都有使用但因为二者都有明显的缺点所以效果不是特别理想。 能否开发一种工具既不需要用户深度配合也不需要提前埋点就能方便、快速地定位线上问题 作为程序员查bug一般使用下面几种方式阅读源码、记录日志或调试程序。一般本地无法复现的问题通过阅读源码很难找到原因而且大多数情况都和用户本地环境有关。记录日志的缺点之前讲过了同样不予考虑那能否像调试本地程序一样调试已经发布出去的程序呢我们对此做了一些尝试和探索。 先看下调试原理这里以Java为例通过IDE调试Android程序也基于此原理。JavaAndroid程序都是运行在JavaDalvik\ART虚拟机上的要调试Java程序就需要向Java虚拟机请求当前程序运行状态并对虚拟机发送一定的指令设置一些回调等等。Java的调试体系就是虚拟机的一套用于调试的工具和接口。Java SE从1.2.2版本以后推出了JPDA框架Java Platform Debugger ArchitectureJava平台调试体系结构。 JPDA框架 JPDA定义了一套独立且完整的调试体系它由三个相对独立的模块组成分别为 JVM TIJava虚拟机工具接口被调试者。JDWPJava Debug Wire ProtocolJava调试协议通道。JDIJava Debug InterfaceJava调试接口调试者。这三个模块把调试过程分解成了三个自然的概念 被调试者运行在我们想要调试的虚拟机上它可以通过JVM TI这个标准接口监控当前虚拟机的信息。调试者定义了用户可以使用的调试接口用户可以通过这些接口对被调试虚拟机发送调试命令同时显示调试结果。在调试者和被调试者之间通过JDWP传输层传输消息。整个过程如下 Components Debugger Interfaces/ |--------------|/ | VM |debuggee ----( |--------------| ------- JVM TI - Java VM Tool Interface\ | back-end |\ |--------------|/ |comm channel -( | --------------- JDWP - Java Debug Wire Protocol\ ||--------------|| front-end ||--------------| ------- JDI - Java Debug Interface| UI ||--------------|下面重点介绍一下JDWP协议。 JDWP协议 JDWP协议是用于调试器与目标虚拟机之间进行调试交互的通信协议它的通信会话主要包含两类数据包 Command Packet命令包。调试器发送给虚拟机Command用于获取程序状态或控制程序执行虚拟机发送Command给调试器用于通知事件触发消息。Reply Packet回复包虚拟机发送给调试者回复命令的请求或者执行结果。JDWP的数据包主要包含包头和数据两部分包头字段含义如下 数据包部分JDWP协议按照功能分为18组命令以Java 7为例包含了虚拟机、引用类型、对象、线程、方法、堆栈、事件等不同类型的操作命令。 Dalvik虚拟机/ART虚拟机对JDWP协议的支持并不完整但是大部分关键命令都是支持的具体信息可以参考Dalvik-JDWP和ART-JDWP中所支持的消息。 Android调试原理 Android调试模型可以看作JPDA框架的具体实现。其中变化比较大的一个是JVM TI适配了Android设备特有的Dalvik虚拟机/ART虚拟机另一个是JDWP的实现支持ADB和Socket两种通信方式ADB全称为Android Debug Bridge是Android系统的一个很重要的调试工具。整体的调试模型如下 ____________________________________| || ADB Server (host) || |Debugger --- LocalSocket ---- RemoteSocket || || ||___________________________||_______|||Transport ||(TCP for emulator - USB for device) ||||___________________________||_______| || || ADBD (device) || || || | Android-VM | || | JDWP-thread LocalSocket - RemoteSocket || ||____________________________________|运行在PC上的ADB Server和运行在Android设备上的ADBD守护进程之间通过USB或者无线网络建立连接分别负责Debugger和Android设备的虚拟机进行通信。一旦连接建立起来Debugger和Android VM通过“桥梁”进行数据的交换ADB Server和ADBD对它们来说是透明的。 综上要实现远程调试关键需要实现两部分功能 能够自定义JDWP通道。能模拟ADB和ADBD实现消息的转发。先看下如何实现自定义JDWP通道。 JDWP启动过程 我们看下Android 5.0系统在启动一个应用时是如何启动JDWP Thread的。 点击图片查看大图 通过上图可以看到Android在创建虚拟机的同时会创建一个JDWP-ThreadJDWP默认有ADB和Socket两种通信方式。要实现远程调试ADB这种方式肯定不适用所以能否实现一个自定义的Socket通道来实现JDWP的消息转发成了问题的关键。 Hack-Native-JDWP 通过阅读JDWP启动源码Android-API-21发现要想让JDWP通过自定义的Socket通道进行通信需要满足两个条件 能够修改全局变量gJdwpOptions的值使其配置为Socket模式并指明对应的端口号。使用新的gJdwpOptions参数重新启动JDWP-Thread。在Android中JDWP相关代码分别被编译成libart.soArt和libdvm.soDalvik。修改或调用其他so库中的代码需要用到动态加载使用动态加载应用程序需要先指定要加载的库然后将该库作为一个可执行程序来使用即调用其中的函数。动态加载API 就是为了动态加载而存在的它允许共享库对用户空间程序可用。下面表格展示了这个完整的 API 函数描述dlopen使对象文件可被程序访问dlsym获取执行了 dlopen 函数的对象文件中的符号的地址dlerror返回上一次出现错误的字符串dlclose关闭目标文件在介绍如何调用动态加载功能之前先介绍一下C/C编译器在编译目标文件时所进行的名字修饰符号化。 符号化 上文提到要想自定义JDWP-Thread首先需要修改gJdwpOptions的值该值是在debugger.cc中通过Dbg::ParseJdwpOptions方法来设置的所以只要用新的配置重新调用一次ParseJdwpOptions即可。 如何找到Dbg::ParseJdwpOptions这个函数地址呢为了保证每个函数、变量名都有唯一的标识编译器在将源代码编译成目标文件时会对变量名或函数名进行名字修饰。 先看一个例子下面的C程序中两个f()的定义 int f (void) { return 1; } int f (int) { return 0; } void g (void) { int i f(), j f(0); }这些是不同的函数除了函数名相同以外没有任何关系。如果不做任何改变直接把它们当成C代码结果将导致一个错误C语言不允许两个函数同名。所以C编译器将会把它们的类型信息编码成符号名结果类似下面的代码 int __f_v (void) { return 1; } int __f_i (int) { return 0; } void __g_v (void) { int i __f_v(), j __f_i(0); }可以通过nm命令查看so文件中的符号信息。 nm -D libart.so | grep ParseJdwpOptions 001778d0 T _ZN3art3Dbg16ParseJdwpOptionsERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE这样就得到了ParseJdwpOptions函数在动态链接库文件中符号化以后的函数名。 找到符号化了的函数名后就可以通过调用动态链接库中的函数重新启动JDWP-Thread。部分代码如下以下代码只针对Android-API-21和Android-API-22版本有效 void *handler dlopen(/system/lib/libart.so, RTLD_NOW);if(handler NULL){LOGD(LOG_TAG,env-NewStringUTF(dlerror()));}//对于debuggable false的配置重新设置为可调试void (*allowJdwp)(bool);allowJdwp (void (*)(bool)) dlsym(handler, _ZN3art3Dbg14SetJdwpAllowedEb);allowJdwp(true);void (*pfun)();//关闭之前启动的jdwp-threadpfun (void (*)()) dlsym(handler, _ZN3art3Dbg8StopJdwpEv);pfun();//重新配置gJdwpOptionsbool (*parseJdwpOptions)(const std::string);parseJdwpOptions (bool (*)(const std::string)) dlsym(handler,_ZN3art3Dbg16ParseJdwpOptionsERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE);std::string options transportdt_socket,address8000,servery,suspendn;parseJdwpOptions(options);//重新startJdwppfun (void (*)()) dlsym(handler, _ZN3art3Dbg9StartJdwpEv);pfun();以上代码关闭了之前可能存在的JDWP-Thread同时开启一个本地的Socket通道来进行通信这样就能通过本地的Socket通道来进行JDWP消息的传递。 突破7.0动态链接的限制 通过上面代码可知实现自定义的JDWP通道主要是采用动态调用libart.so/libdvm.so中的函数实现。但从 Android 7.0 开始系统将阻止应用动态链接非公开 NDK库详情请参考《Android 7.0行为变更》强制调用会产生如下Crash java.lang.UnsatisfiedLinkError: dlopen failed: library /system/lib/libart.so needed or dlopened by /system/lib/libnativeloader.so is not accessible for the namespace classloader-namespace如何绕过这个限制来动态调用libart.so中的方法既然直接调用dlopen会失败那是不是可以模拟dlopen和dlsym的实现来绕过这个限制 dlopen和dlsym分别返回动态链接库在内存中的句柄和某个符号的地址所以只要能找到dlopen返回的句柄并通过句柄找到dlsym符号对应的地址就相当于实现了这两个函数的功能。libart.so会在程序启动之后就被加载到内存中可以在/proc/self/maps找到当前进程中libart.so在内存中映射的地址 vbox86p:/ # cat /proc/1665/maps | grep libart.so e2d50000-e3473000 r-xp 00000000 08:06 1087 /system/lib/libart.so e3474000-e347c000 r--p 00723000 08:06 1087 /system/lib/libart.so e347c000-e347e000 rw-p 0072b000 08:06 1087 /system/lib/libart.so这里libart.so被分成了三个连续子空间从e2d50000开始。 如何才能在内存中找到想要打开的函数地址我们先看下ELF文件结构 要实现dlsym首先要保证查找的符号在动态符号表中能找到在ELF文件中SHT_DYNSYM对应的Section定义了当前文件中的动态符号SHT_STRTAB定义了动态库中所有字符串SHT_PROGBITS则定义了动态库中定义的信息。如何找到这些Section 通过内存映射的方式把libart.so映射到内存中按照ELF文件结构解析映射到内存中的libart.so解析SHT_DYNSYM并把当前section复制到内存中解析SHT_STRTAB并把当前section复制到内存中后面需要根据SHT_STRTAB来找到特定的符号解析SHT_PROGBITS得到当前内存映射的偏移地址这里要注意不同进程中相同动态库的同一个函数的偏移地址是一样的。以上逻辑的部分代码片段如下 fd open(libpath, O_RDONLY);size lseek(fd, 0, SEEK_END);if(size 0) fatal(lseek() failed for %s, libpath);elf (Elf_Ehdr *) mmap(0, size, PROT_READ, MAP_SHARED, fd, 0);close(fd);fd -1;if(elf MAP_FAILED) fatal(mmap() failed for %s, libpath);ctx (struct ctx *) calloc(1, sizeof(struct ctx));if(!ctx) fatal(no memory for %s, libpath);//通过/proc/self/proc 找到的libart.so的起始地址ctx-load_addr (void *) load_addr;shoff ((char *) elf) elf-e_shoff;for(k 0; k elf-e_shnum; k) {shoff (char *)shoff elf-e_shentsize;Elf_Shdr *sh (Elf_Shdr *) shoff;log_dbg(%s: k%d shdr%p type%x, __func__, k, sh, sh-sh_type);switch(sh-sh_type) {case SHT_DYNSYM:if(ctx-dynsym) fatal(%s: duplicate DYNSYM sections, libpath); /* .dynsym */ctx-dynsym malloc(sh-sh_size);if(!ctx-dynsym) fatal(%s: no memory for .dynsym, libpath);memcpy(ctx-dynsym, ((char *) elf) sh-sh_offset, sh-sh_size);//ctx-nsyms 动态符号表的个数ctx-nsyms (sh-sh_size/sizeof(Elf_Sym)) ;break;case SHT_STRTAB:if(ctx-dynstr) break; /* .dynstr is guaranteed to be the first STRTAB */ctx-dynstr malloc(sh-sh_size);if(!ctx-dynstr) fatal(%s: no memory for .dynstr, libpath);memcpy(ctx-dynstr, ((char *) elf) sh-sh_offset, sh-sh_size);break;//当前段内容为program defined information程序定义区case SHT_PROGBITS:if(!ctx-dynstr || !ctx-dynsym) break;//得到偏移地址ctx-bias (off_t) sh-sh_addr - (off_t) sh-sh_offset;break;}}//关闭内存映射munmap(elf, size);接下来就可以根据要找的符号名在SHT_DYNSYM中对应的位置得到具体的函数指针部分代码如下 void *fake_dlsym(void *handle, const char *name) {int k;struct ctx *ctx (struct ctx *) handle;Elf_Sym *sym (Elf_Sym *) ctx-dynsym;char *strings (char *) ctx-dynstr;for(k 0; k ctx-nsyms; k, sym)if(strcmp(strings sym-st_name, name) 0) {//动态库的基地址 当前符号section地址 - 偏移地址return (char *)ctx-load_addr sym-st_value - ctx-bias;}return 0; }通过以上模拟dlopen和dlsym的逻辑我们成功绕过了系统将阻止应用动态链接非公开 NDK库的限制。 消息转发 完成上面逻辑以后就可以通过本地Socket在虚拟机和用户进程之间传递JDWP消息。但是要实现远程调试还需要远程下发虚拟机的调试指令并回传执行结果。我们通过App原有Push通道加上线上消息转发服务实现了整个调试工具的消息转发功能 Proguard对调试的影响 正常发布到市场的项目都会通过Proguad进行混淆不同力度的混淆配置会生成不同的字节码文件。对调试功能影响比较大的配置有两个 LineNumberTableLocalVariableTable如果Proguard中没有对这两个属性进行Keep那经过Proguard处理的方法字节码中会缺失这两个模块对调试的影响分别是无法在方法的某一行设置断点和无法获取当前本地变量的值但能获取到方法参数变量和类成员变量。一般为了在应用发生崩溃时能获取到调用栈中每个函数对应的行号需要保留LineNumberTable同时为了减少包体积会放弃LocalVariableTable。在没有LocalVariableTable的情况下可以通过调用Execute命令得到一些运行时结果间接得获取到本地变量。 JDI的实现 整个消息交互流程跑通以后接下来要做的就是根据JDI规范作进一步的封装。为了方便快速调试目前调试工具的前端实现主要参考了LLDB的调试流程通过设置命令的方式进行调试整体样式如下图所示 点击图片查看大图 本文从调查线上问题的常见手段入手介绍了到店餐饮移动团队在实现远程调试过程中的尝试和探索。通过远程调试可以方便快捷地获取用户当前App运行时的状态助力开发者快速定位线上问题。 Java Debug Write Protocol.Android虚拟机调试器原理与实现.Linux 动态库剖析.Nougat_dlfunctions.Dynamic Linking.武智Android高级开发工程师2013年7月校招加入美团目前负责维护大众点评App的美食频道。 莹莹2015年校招加入美团主要参与大众点评美食频道的日常开发工作专注于通过工具自动化地提高开发效率和质量。 周佳、卢晟、永锋2016年校招加入美团主要参与大众点评美食频道的日常开发工作。 到店餐饮技术部交易与信息技术中心负责美团美食用户端业务服务于数以亿计用户通过更好的榜单、真实的评价和完善的信息为用户提供更好的决策支持致力于提升用户体验。我们同时承载所有餐饮商户端线上流量为餐饮商户提供多种营销工具提升餐饮商户营销效率最终达到让国人“Eat Better、Live Better”的美好愿景我们的团队包含且不限于Android、iOS、FE、Java、PHP等技术方向已完备覆盖前后端技术栈。只要你来就能点亮全栈开发技能树。诚挚欢迎投递简历至wangying49#meituan.com。
http://wiki.neutronadmin.com/news/245728/

相关文章:

  • 旅游公司电子商务网站建设策划书建筑工程网络计划技术与应用
  • 怎样做淘宝券网站建站平台营销
  • 深圳做网站公司有哪些seo和网络推广哪个好
  • 网站设计包含哪些技术做爰全过程免费的视频99网站
  • 网站的欢迎页怎么做简易的网站模板
  • 毕业设计做一个网站怎么做北京网站设计工资多少
  • 网站百度收录查询seo权威入门教程
  • 做网站教程pdf哪个dns访问国外网站
  • 南昌网站建设资讯百度推广管理平台
  • wordpress建立论坛网站专门做网站建设的公司
  • 美食网站的建设目的搜狗推广登录入口
  • 怎么样自己建设网站[ 1500元做网站_验收满意再付款! ]_沛县网络公司
  • 代做设计网站网站分享代码怎么加
  • 站长工具seo综合wordpress能恢复数据库
  • 网站推广服务chuseo北京网站建设 app
  • 海南行指三亚网站开发最权威最有效的投诉平台
  • 网站开发设计步骤什么样的网站快速盈利
  • 衡阳seo优化seo推广官网
  • 清远网站seo新开最好的传奇网站
  • 可以做h5的网站湖北企业建站系统平台
  • 基层组织建设部 网站怎样用别人的网站做修改病句
  • vs2013做网站保存的格式如何评价网站是否做的好
  • 网站综合查询工具做网站的公司有多少家
  • 国外免费网站服务器可以做调查问卷的网站
  • 找熟人做网站的弊端网站管理和建设工作职责
  • 东莞大岭山做网站公司网站开发强制使用急速内核
  • 已有备 网站新增网站图片展示网站建设
  • 做网站需要注意哪些域名怎么解析到网站
  • 网站seo方案建设目标e4a怎么做点击跳转网站
  • 求2021没封的良心网站网页设计与网站建设课后习题答案