前言 前些时候研究脚本混淆时,打算先学一些「程序流程」相关的概念.为了不因太枯燥而放弃,决定想一个有趣的案例,可以边探索边学. 于是想了一个话题:尝试将机器指令 1:1 翻译 成 JavaScript,这样就能在浏览器中,直接运行等价的逻辑. 为了简单起见,这里选择古董级 CPU -- MOS 6502. 本系列陆续更新了 8 篇,前面几篇只是理论分析: 跳转处理 流程分割 动态跳转 指令变化 深度优化 原本只打算遐想一下,分析下可行性而已.不过,后来发现实现也不难,于是又补了两篇: 过渡语言…
上一篇,我们发现大多数 6502 指令都可以直接 1:1 翻译成 JS 代码,但除了「跳转指令」. 跳转指令,分无条件跳转.条件跳转.从另一个角度,也可分: 静态跳转:目标地址已知 动态跳转:目标地址运行时才知道 为了让问题更简单,本文只讨论「静态跳转」,并且是在固定的指令之间跳转. 我们用 JXX 表示跳转指令,XXX 表示其他指令: L1: XXX JXX L1 - XXX JXX L2 + XXX L2: XXX 这里 JXX L1 跳转到之前的位置,暂且称为「负跳转」.相应的,JXX L…
上一篇 我们讨论了跳转指令,并实现「正跳转」的翻译,但最终困在「负跳转」上.而且,由于线程模型的差异,我们不能 1:1 的翻译,必须对流程进行一些改造. 当初之所以选择翻译,而不是模拟,就是出于性能考虑.但是,这并不意味绝对不能用模拟 -- 如果能用少量模拟,解决一些技术障碍,那也是值得的. 现在,我们尝试用模拟的方式,来控制流程. 流程模拟 JS 的流程控制,就好比排队:如果你不想排了,可以随时退出,大家都不介意:但是之后又想回来插队,这就不行了,你必须从头排起.所以,只能退出,不能插入. 插…
上一篇,我们用模拟流程的方式,解决了跳转问题. 不过静态跳转,好歹事先是知道来龙去脉的.而动态跳转,只有运行时才知道要去哪.既然流程都是未知的,翻译从何谈起? 动态跳转,平时出现的多吗?非常多!除了 JMP 指令,还有一个更常用的,就是 RTS 指令. 它用于子流程的返回 -- 从栈上取出数据给程序计数器 PC,回到之前执行 JSR 指令的位置(相当于 call / return).如果把栈上数据改了,那也是可以任意跳转的. 动态跳转很常用,因此必须得支持. 已有流程 动态跳转,理论上可以跳到任…
上一篇,我们通过内置解释器的方案,解决任意跳转的问题.同时,也提到另一个问题:如果指令发生变化,又该如何应对. 指令自改 如果指令加载到 RAM 中,那就和普通数据一样,也是可以随意修改的.然而,对应的 JS 是事先翻译好的,已经不能改了.如果运行时突然变卦,那相应的 JS 就作废了 -- 如果修改的是跳转指令,甚至会影响已划分的流程.所以为了保守起见,指令被改后进入模拟状态. 因此,我们得监控指令区的修改: function store(addr, val) { // 修改指令区 if (0x…
第一篇 中我们曾提到,JavaScript 最终还得经过浏览器来解析.因此可以把一些优化工作,交给脚本引擎来完成. 现代浏览器的优化能力确实很强,但是,运行时的优化终归是有限的.如果能在事先实现,则可投入更多资源,优化得更充分. 优化尝试 指令 1:1 翻译成 JS,结果显然会存在一些累赘的逻辑. 例如状态标志,很多时候是不必计算的: A = read(10) // LDA 10 SR_N = ... // 无意义,可以去除 SR_Z = ... X = read(20) // LDX 20 S…
上一篇,我们决定使用 LLVM 来优化程序,并打算用 C 作为输入语言.现在我们来研究一下,将 6502 指令转换成 C 的可行性. 跳转支持 翻译成 C 语言,可比 JS 容易多了.因为 C 支持 goto,跳转可轻松实现.例如: $0600 LDA #$01 $0602 STA $02 $0604 JMP $0600 就能翻译成如下 C 代码: L_0600: A = 0x01; ... L_0602: write(A, 0x02); L_0604: goto L_0600; 我们把指令所在…
上一篇,我们顺利将 6502 指令翻译成 C 代码,并演示了一个案例. 现在,我们来完成最后的目标 -- 转换成 JavaScript. 中间码输出 我们之所以选择 C,就是为了使用 LLVM.现在来看看,生成的 LLVM 中间表示: 不难看出,顺序执行的逻辑都在一个 label 中,跳转则用 br 符号. 这种风格,和我们之前讨论的指令切割非常相似.一个 label 块,正好翻译成一个 block_xxx 的 JS 函数. 所以,理论上翻译成 JS 并不困难,写一个 LLVM backend…
回到目录 Entityframeworks很聪明 不错,非常不错!ef里的contains比linq to sql里的contains有了明显的提升,事实上,是在进行SQL语句翻译上有所提升,在linq to sql里不支持iqueryable的contains集合,它只支持本地集合进行contains,而本地集合的contains会被.net翻译成sql语句是where in (...),即集合有多个元素,在in里就会被列举多少次,这个在性能上是非常低下的,不提倡的,而且它还有长度限制,最多本…
Delphi里做了魔法变化,每个变量名称本身就是指针,因为不怎么需要指针语法.我也不知道是不是因为这个原因引起的Delphi与VC对句柄的不同处理. 这是Delphi的强行关机函数,好用,调用方式:WindowsExit(EWX_POWEROFF or EWX_FORCE) function WindowsExit(RebootParam: Longword): Boolean; var TTokenHd: THandle; TTokenPvg: TTokenPrivileges; cbtpPr…