Sc_lou 发表于 2014-2-5 12:14:21

自修改程序教程(在程序中储存数据)

本帖最后由 Sc_lou 于 2014-2-5 12:17 编辑

Deep Thought’s SMC Tutorial (Saving Data in a Program)
翻译:ThomazL
转载请著名出处「Ti84/83」SMC教程
什么是SMC?你可能发现在某些汇编程序中(像Phoenix)可以在不创建appvar或其他的变量的前提下保存用户的设置,最高分,甚至是游戏的暂停状态,他们可以直接将这些信息储存在程序的本身中。这叫做自修改代码self-modifying code。通过使用SMC程序员们可以写出在运行中自动修改程序代码的程序,但这通常很复杂,特别像AXE之类的已经编译完地语言,程序员们无法自由地控制编译完成地机器码。但是在AXE中,数据是很容易控制的。在AXE中,人们基本上都在处理单独的字节和指针,因为这些字节和指针是你自己写的,你应该知道它们分别代表着什么。“这只是一些数据而已”程序中的数据和一般的变量中的数据差不多,我们可以在使用2个字节来表示GDB0指向的最高分:https://raw.github.com/Ft-lou/geekota.github.io/master/img/smc_1.gif记住,任何字符串或16进制的数据或在’Data(‘命令中的数据都储存在程序中,而且你将当它们“储存”到一个静态变量(像GDB0,Pic0和Str0…)时这些变量会变成像其他指针(L1…)地指针。因此在程序中控制变量就变得十分简单,剩下的就是考虑让这些数据在程序下一次运行仍然保留的方法。但事情在此时就变得麻烦起来了,因为用户运行程序的方式会对SMC是否保留产生很大地影响。最简单的情况是程序在RAM里而且用户通过Shell(像Ion,MiargeOS或DoorsCS)来运行。在这种情况下,只程序只会产生一份备份(即程序本身),而且你做出的任何改动都是永久的。如果程序在archive中,就不这么简单了,因为程序永远不会从archive中直接运行,shell会在RAM中生成一份对程序的临时备份然后从那里运行。有些shell(像MirageOS和DoorsCS)可以“重写”备份会archive—原理是将原来的程序删除然后把RAM中地那份程序备份放进archive,带上你对其做出的任何更改。如果用户用没有“重写”功能的shell运行程序(或者用户关闭了“重写”功能),用户做出的改变都会随着RAM中临时备份的删除而消失。记住这都取决于用户对计算器的设置,你不能强制用户们开启某些他们不想开启的功能…简单的情况对使用shell的人们来说,这会做:https://raw.github.com/Ft-lou/geekota.github.io/master/img/smc_2.gif程序第一次运行时,最高分会显示为0,如果用shell运行,它则在之后的每一次显示65535.不幸的是,不是每个人都会使用shell来运行程序。如果你将源代码编译成特定shell才能运行的格式的话就没什么问题,但如果你想做一个好人然后将源代码编译成Ion格式(因此所有人都能运行它,无论有没有用shell),你得想办法处理诡异的Asm(命令。这是因为不管怎么样,Ti-OS会生成对程序的一份备份,甚至当程序在RAM中时。这会导致一些问题。首先,这意味着当空闲RAM空间小于程序大小时(即使程序在RAM中),人们不能运行此程序(自己试试)。第二,这意味着如果你想用SMC,你必须要同时处理两份程序代码,另一份会在RAM中的任何地方…Complications你可以使用GetCalc(命令来寻找那份备份,找到GDB0中两字节在那个变量中的位置然后将最高分储存在那里。但GetCalc(命令需要一个寻找变量中包涵的字符串才能工作,你可以假设他叫你编写的程序的名字(prgmAWESOMEG)。但是当你的用户自己改了程序名字之后,你就没辙了。而且在当今shell中的计算器世界中,为程序改名简直小菜一碟。幸运的是,当此程序从主界面的Asm(命令中运行,此程序的全名会被储存在某一个特定的位置。这个特定的位置叫做OP1,在E8478里,但因为这个位置被用作处理很多事,从变量管理到浮点运算,这个位置很快就会被覆盖。但如果你不创建,(un)archive或删除你程序中的任何变量,那份“备份”就会一直储存在那个位置上,因此如果你将一个指针指向那个位置,你就可以在之后的工作中运用它:https://raw.github.com/Ft-lou/geekota.github.io/master/img/smc_3.gif这样就没问题了,现在我们知道Q指向程序的那份“备份”,不过我们怎么知道应该将最高分储存在这份备份正确的位置呢?如果你没有正确储存,你就可能覆盖程序的一些代码,这就会变得非常糟糕。我们至少知道正在运行的程序中最高分储存在叫做GDB0的地址,我们也知道正在运行的程序开始于地址E9D93,因为运行的程序都会被移至这个地址。因此我们可以从GDB0中减掉E9D93,然后我们就会得到一个最高分在程序中的偏移量(即我们储存最高分的位置)。如果你对上面的工作不怎么清楚,想象你正在给某人指去你家的路。你知道你家在街道里那个区块但你不知道你家到街道底部的距离,但你知道你家的GPS定位坐标,精确到米,而且你也知道街道底的GPS定位坐标,同样精确到米。因此你只要从一个位置减去另一个位置你就可以知道你家里街道底有多远了…这里运用了相同的原理——你知道“最高分”的绝对位置(就是GDB1),你也知道程序开端的绝对位置(就是E9D93),你所要做的就是减法,然后你就知道“最高分”相对于程序开端的位置了!现在想象一下,你的街道被风从地球表面刮起然后降落在了西伯利亚,营救队知道街道降落的绝对位置,现在你要告诉营救队你的绝对位置他们才能来救你(在我们的情况中,街道降落在的位置信息被储存在了Q里)。在程序左后加上这一行:https://raw.github.com/Ft-lou/geekota.github.io/master/img/smc_4.gifMore complications现在程序对那些用“Asm(”的用户能产生效果了,但它会对用shell的用户们产生问题,应为shell不会把程序名放到E8478的位置…我们必须对程序进行一些修改,不然当用户们从shell中运行程序是就会在RAM中不知道的地方写下2字节的信息。(为何不能好好生存?!!)有一个可行的方法就是用magic number。简单来说,我们将一个已知的值放在程序中的一个已知的位置,在我们保存到{Q+GDB0-E9D93}之前,我们可以检查一下相对于Q储存的位置周围的位置,如果值不同的话,那么Q就没有指向我们需要的位置,在这个情况下,我们就不能在哪里储存“最高分”。在程序开端加上这一句话:https://raw.github.com/Ft-lou/geekota.github.io/master/img/smc_5.gif我选择了D1ECA510这一个16进制字符串应为它在程序中十分少见,因此产生Bug的几率就会变得很低,你换一个值也可以,应该说你必须换一个值,不然的话人们都用同一个值,就会提高产生Bug的几率。(用0组成的字符串是一个非常差的选择…)现在吧程序最后一行改成这样:https://raw.github.com/Ft-lou/geekota.github.io/master/img/smc_6.gif记住,字节被反转了因为Z80处理器是little-endian————两个字节的值在储存和阅读时会把最低有效位放在前面,你不需要担心太多…最终程序:https://raw.github.com/Ft-lou/geekota.github.io/master/img/smc_7.gif优化之后的最终程序:https://raw.github.com/Ft-lou/geekota.github.io/master/img/smc_8.gif总结一下
Program in RAMProgram in archive
Shell with writebackModifications savedModifications saved
Shell without writebackModifications savedModifications discarded
Asm(Save modifications manuallyN/A

页: [1]
查看完整版本: 自修改程序教程(在程序中储存数据)