JavaScript 现代化排错实践 赵劼 - 2012.9
关于我 • 赵劼 / ⽼老赵 / Jeffrey Zhao / 赵姐夫 • ⽇日写代码三百⾏行,不辞⻓长作程序员 • 博客:http://blog.zhaojie.me/ • 微博:@⽼老赵 • F#, JavaScript, Scala, C#, Python, .NET, Mono... • 痛恨Java语⾔言
内容提纲 • 代码调试检查 • 调⽤用堆栈分析 • 调试混淆代码 • Source Map
代码调试
代码调试 • 很久很久以前:alert • 过了⼀一段时间:console.log • 其实从IE 5时代开始便可以调试代码
两个问题 • Node.js可以调试吗? • eval出来的代码可以调试吗?
调试Node.js代码 • Eclipse • Eclipse Debugger Plugin for V8 • 启动程序 • node  -­‐-­‐debug[=port]  app.js • node  -­‐-­‐debug-­‐brk[=port]  app.js Using Eclipse as Node Applications Debugger
调试eval的代码 • 代码末尾://@  sourceUrl=<path> • ⽀支持浏览器 • Chrome • Safari • Firefox (Firebug)
调⽤用堆栈分析
调⽤用堆栈
命名函数表达式 //  函数定义 function  Identifier(FormalParameterListopt)  {  FunctionBody  } //  函数表达式 function  Identifieropt(FormalParameterListopt)  {  FunctionBody  } 命名函数表达式探秘
分辨⼀一下 //  声明:程序的⼀一部分 function  foo()  {  }; //  表达式:分组操作符中只能包含表达式 (function  foo()  {  }); //  表达式:赋值表达式(AssignmentExpression)的⼀一部分 var  bar  =  function  foo()  {  }; //  表达式:New表达式(NewExpression)的⼀一部分 new  function  bar()  {  }; (function  ()  {        //  声明:函数体(FunctionBody)的⼀一部分        function  bar()  {  }; })();
调试混淆代码
调试混淆代码
格式化以后
配合Source Map
Source Map • 记录⺫⽬目标代码到源代码的映射 • JSON格式 + 编码后的映射数据 • Source Map V3 Spec
Source Map • 记录⺫⽬目标代码到源代码的映射 • JSON格式 + 编码后的映射数据 • Source Map V3 Spec 脚本末尾加上://@  sourceMappingURL=<path>,或 脚本请求头加上:X-­‐SourceMap:  <path>
深⼊入Source Map
Source Map V3 {        "version":  3,        "file":  "all.min.js",        "lineCount":  37,        "sources":  [                  "wind-­‐core.js",                "wind-­‐builderbase.js",                "wind-­‐async.js",                "sorting-­‐animations.aot.js"        ],        "names":  [  "Wind",  "_",  "isArray",  "obj",  ...  ],        "mappings":  "AAAC,SAAS,EAAG,CAGT,IAAIA,  ..." }
解码 mappings 字段 • ⽤用分号区分“⾏行”,逗号区分“段”。 • Base64 Variable-Length Quantity 编码 • 节省空间,⽐比V2节省50%左右
确定代码⾏行号 AABBC;                  //  第1⾏行 KAUYM,GAKoEF;    //  第2⾏行 CCDD,  ...;          //  第3⾏行 ... ...
Base64 VLQ解码 KAUYM,GAKoEF
Base64 VLQ解码 KAUYM,GAKoEF >> [10,  0,  20,  24,  12],  [6,  0,  10,  40,  4,  5]
Base64 VLQ解码 KAUYM,GAKoEF >> [10,  0,  20,  24,  12],  [6,  0,  10,  40,  4,  5] >> [001010,  000000,  010100,  011000,  001100],   [000110,  000000,  001010,  101000,  000100,  000101]
Base64 VLQ解码 KAUYM,GAKoEF >> [10,  0,  20,  24,  12],  [6,  0,  10,  40,  4,  5] >> 最低N-­‐1位为数据位 [001010,  000000,  010100,  011000,  001100],   01000,    00100 [000110,  000000,  001010,  101000,  000100,  000101] 最⾼高位表⽰示是否“连接后续数据”
Base64 VLQ解码 KAUYM,GAKoEF >> [10,  0,  20,  24,  12],  [6,  0,  10,  40,  4,  5] >> 最低N-­‐1位为数据位 [001010,  000000,  010100,  011000,  001100],   01000,    00100 [000110,  000000,  001010,  101000,  000100,  000101] 最⾼高位表⽰示是否“连接后续数据” >> [1010,  0,  10100,  11000,  1100],   [110,  0,  1010,  10001000,  101]
数据解码 [1010,  0,  10100,  11000,  1100],   [110,  0,  1010,  10001000,  101]
数据解码 [1010,  0,  10100,  11000,  1100],   [110,  0,  1010,  10001000,  101] 最⾼高N-­‐1位为数据位 最低位为符号位
数据解码 [1010,  0,  10100,  11000,  1100],   [110,  0,  1010,  10001000,  101] 最⾼高N-­‐1位为数据位 最低位为符号位 >> [5,  0,  10,  12,  6],  [3,  0,  5,  68,  -­‐2]
数据解码 [1010,  0,  10100,  11000,  1100],   [110,  0,  1010,  10001000,  101] 最⾼高N-­‐1位为数据位 最低位为符号位 >> [5,  0,  10,  12,  6],  [3,  0,  5,  68,  -­‐2] >> [5,  0,  10,  12,  6],  [8,  0,  15,  80,  4]
含义 //  已确定⾏行号 [  5,      //  列号  0,      //  源⽂文件,从sources查找  10,    //  源⽂文件内⾏行号  12,    //  源⽂文件内列号  6        //  源⽂文件内标⽰示符,从names查找 ]
⽆无需⼿手动分析 • Google Closure Compiler可以⽣生成V2版 Source Map格式,未编码的明⽂文数据 • 使⽤用Mozilla的SourceMap项⺫⽬目读取或⽣生成 Source Map⽂文件
总结 • 使⽤用调试器辅助JavaScript开发 • 即使是eval⽣生成的代码也没有问题 • 使⽤用命名函数表达式来美化堆栈 • 使⽤用Source Map来调试混淆后的代码 • ⼿手动使⽤用Source Map亦不是问题
Q &A
谢谢

JavaScript现代化排错实践