ASM初级教程
合集下载
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
的这
,一
不段
用 理 会
是 给 编
译
器
看
main:@这是一个标签 push {r0-r1, lr} ldr r0, .PLAYER_DATA
寄存器中 ldr r0, [r0] ldr r1, .VAR ldrh r0, [r0, #0xC]
于 r0 strh r0, [r1] pop {r0-r1, pc}
正如前面所说,ASM 代码操纵的是 CPU,辅助的还有整片内存区域 (包含各个子区,ROM 其实也算在其中了)。ASM 代码是通过存取、 计算各种数据来完成我们所需要的一切功能的。
现在来看看刚才我们所写代码完成功能,是如何进行的:
.text .align 2 .thumb .thumb_func .global lesson1
.align 2 .PLAYER_DATA:
.word 0x0300500C .VAR:
.word 0x020270B6 + (0x800D * 2)
另存为“lesson.asm”,注意,扩展名也改为 asm 了。
现在,下载 thumb.zip(点击这里),解压到你的 D 盘根目录下,并保 持里面的三个文件不变。
#org @secret = Sshh! I'll tell you a secret[.]\nYour Secret ID is [buffer1]! ’这里也需要特别 说明一下,buffer1 其实就是 buffer 命令当中的第 0 号缓冲区,没有[buffer0]哦。
好了,以上是我们初入 ASM 领域所见的第一个应用的解释,但实际
.align 2@不必理会 .PLAYER_DATA:@另一个标签
.word 0x0300500C .VAR:@还是一个标签
.word 0x020270B6 + (0x800D * 2)
更详细的解释:
首先,push 命令将三个寄存器压入环境。
紧接着,从指定的内存地址当中读出一个值放入寄存器 r0,其后,从 r0 所指的地址再读出内容来。这一步看起来很多余,按照一般的想法, 直接从那个地址读不就可以了么?很遗憾,任天堂为了对抗金手指, 特地将这些值的地址做了动态处理,也就是 DMA 技术,所以连这些 值的地址,都是不固定的,需要先获得地址,再从地址中得到值。
这颗 CPU 支持 15 个寄存器(r1-r15),同时还有一个状态寄存器(cpsr) 和一个状态保护寄存器(spsr),以及 8 个标志位。
寄存器是什么?寄存器是一种 CPU 自带的存储空间,这个存储空间 访问的速度可谓飞快,比 CPU 去访问内存要快许多。因此在 ASM 当 中,一般都是优先使用寄存器的。但寄存器有它的局限之处,GBA 的这颗 CPU 中我们通常只能使用前 13 个寄存器,后两个寄存器一 个是用来储存子函数返回地址,一个用来记录当前处理地址。 其中 r 多少是对寄存器的称呼。
第二部分:ASM 代码是如何做到的?
在第一部分当中,我们已经看到了,通过 ASM 能够读出原本脚本命 令读不出的东西。那么,这是如何做到的呢?
首先我们来考虑一下,脚本能做些什么吧。 现有的脚本命令大约有不到 250 个,这些脚本命令都是 ROM 当中内 臵的,是游戏的设计人员为剧情编辑人员所准备的,这使得游戏的剧 情编辑不必去理会高深的 ASM 语言,同时又可以轻而易举地将剧情 用简单的代码表达出来。 每一个脚本命令都是“一大堆”ASM 代码的一个集合。游戏引擎内 部包含一个脚本命令解释器,当一个脚本命令被输入其中,解释器就 会呼叫与其相对应的 ASM 代码,然后让 CPU 去执行。而且通常, 相对应的 ASM 代码会比较庞大。 ASM 代码又去做什么了呢?它们一般都是从 ROM 的一个地址,取 出一些数值,然后放在内存的某处,再做各种复杂的运算,然后结束。 所以如果我们自己去写一段 ASM 代码,就能够直接执行我们想做的 事情,也就是直接操纵 CPU,而不需要再经过脚本解释系统了。 有趣的是,要想在脚本当中让 ASM 代码乱入做一些事情,就必须通 过命令 callasm 来做媒介。
各个寄存器用途总结如下:
R0-R12:这部分是通用寄存器,也就是我们一般做运算、小规模存 储用的,这其中,在 THUMB 模式下只有 R0-R7 可以自由使用,被称 为低寄存器,而 R8-R12 只有部分指令才可以使用; R13(别名 SP):THUMB 模式下的栈指针; R14(别名 LR):链接寄存器,储存子函数返回地址; R15(别名 PC):计数器,记录当前处理地址
这是从 0x0300500C 向后 24 个字节的内容。所以我们通过给
0x0300500C 增加 0xC,正好就可以取到私有编号的位臵。
有了这张内存参照表,我们获得其它内容也是很容易的。
现在再来看看脚本到底做了些什么:
#dynamic 0x800000 ‘动态地址起始
#org @start ‘也不多费口舌了 callasm A+1 ‘这句命令呼叫你写入的 ASM 代码,在执行完后回到脚本当中 buffernumber 0x0 LASTRESULT ‘将 LASURESULT 的值按照数字进行转换,也就是将 16 进制 转换为 10 进制,同时将结果写入第 0 号缓冲区。 msgbox @secret 0x2 end
这是因为,ROM 中的一切代码(thumb),都是以 4 个 16 进制位为 基本单位的,所以为了保持和上下文连贯,必须遵循规定。
好了,满足规定找到地址并写入之后,记下地址,假设这个地址为 A。
现在拿出 XSE 来,写下如下的脚本:
#dynamic 0x800000 #org @start callasm A+1’这里写你自己的 ASM 代码地址并+1。 buffernumber 0x0 LASTRESULT msgbox @secret 0x2 end #org @secret = Sshh! I'll tell you a secret[.]\nYour Secret ID is [buffer1]!
以上的话比较抽象,下面我运行一段示例代码,让大家体验一下 ASM。 为了有好的效果,请跟我一起做:
首先,新建并打开一个 txt 文本文档,在其中写入以下内容:
.text .align 2 .thumb .thumb_func .global lesson1
main: push {r0-r1, lr} ldr r0, .PLAYER_DATA ldr r0, [r0] ldr r1, .VAR ldrh r0, [r0, #0xC] strh r0, [r1] pop {r0-r1, pc}
打开“开始”-“运行”,输入“cmd”。
在打开的窗口中输入“D:”
现在你的 cmd 应Байду номын сангаас已经到达 D 盘根目录了。于是我们键入以下内容: “thumb lesson.asm lesson.bin”。
如果你的情况正常,那么就会看到以下的内容: D:\>thumb lesson.asm lesson.bin Assembled successfully.
上我们还不了解这些看起来很费解的命令,比如 str,没关系,下一
节就是理论课了。
第三部分:理论课
ASM 说到底几乎是对硬件的直接操作,尤其是对 CPU 的操作。所以 在此,我们必须对 GBA 的硬件体系有一个明确的了解和理解。
GBA 拥有一颗 ARM7 的 32 位宽 CPU,频率为 16.78MHz,这颗 CPU 可以执行两种不同的但很相似的 GBA 指令类型,一种是 ARM 类型, 一种是 THUMB 类型。除此之外,它还可以执行 GB 以及 GBC 所使 用的 Z80 指令类型。不过 Z80 将不是我们讨论的对象。
2010
ASM 初级教程
新编口袋改版教程系列
——您了解 ASM 的优秀道路之一
liuyanghejerry 口袋社区 2010-6-29
ASM 初级教程
序
2 年前大约这个时候,我开始写脚本教程。很遗憾,直到现在,能够 流利使用脚本的人还是寥寥。 ASM 是比脚本更深入游戏核心的东西,它能够从源码级别控制游戏 的所作所为。正因为如此,使用 ASM 技术更加危险。所以当您阅读 这篇教程时,请务必保证自己已经拥有了充足的基础知识,因基础知 识不牢固而造成的问题,我不会多费口舌。
C 语言源代码
专用的编译器
二进制机器码
在这之中,我们并没有看到有汇编代码出现,这是因为,CPU 并不 认识汇编代码,而只认识机器码。但为了搞清楚机器码每一步究竟是 在做什么,我们通常将这些二进制机器码反编译为汇编语言,然后进 行解读。
为了修改方便,现在的反编译器也是双向的,就是说,可以将二进制 机器码反编译为汇编语言,同样也可以将你自己写的汇编代码转换为 二进制机器码。这样,就可以直接将编译结果写入 ROM 运行了。
于此同时,我自己也是 ASM 领域的初心者,如果文中有疏漏错误之 处,还恳请各位不吝指出。
再次提醒,阅读本教程需要您有充足的基础知识。
第一部分:感性地认识一下 ASM
ASM 是一个英文的缩写,也就是 Assembly 的缩写,翻译成中文, 就是“汇编”。 任何 GBA ROM 都是一个程序。它通常由 C 语言进行编写,经过编 译器,最终成为机器语言代码,其过程如下:
随后,把 0x020270B6 + (0x800D * 2)=0x020370D 这个值存入了 r1 寄存器。这里需要注意,第一,这个内存地址是脚本当中变量 0x800D (也就是 LASTRESULT)的真实内存地址;第二,之所以写成一个式 子,而不是一个值,是因为我们从这个公示当中,恰好可以看到这个 地址的来历。另外,只有 0x8000 系列的几个变量才能用这个公式导 出,普通的变量的地址实际上也是动态值。
@将 r0,r1,lr 三个寄存器带入环境,也就是压入 @从.PLAYER_DATA 标签下的内存地址中读出内容并存于 r0
@将 r0 所指的内存中的值,读入 r0 @将.VAR 标签下的值,读入 r1 @给 r0 加上 0xC,将其视为内存地址再从中读出内容并保存
@将 r0 的内容保存至 r1 所指向的内存当中 @将这三个寄存器带出环境,也就是弹出
很好,此时,你会在 D 盘下看到一个 lesson.bin 文件。
现在用 WinHex 打开它,将其全部复制,并写入到一个火红美版 ROM
当中。
写入的时候要特别注意,ASM 代码对写入时的地址有很大的限制, 地址的最后一位必须是 0 或 4 或 8 或 C。
不要轻视这则规定,一旦你违规了,你所写的代码将不能够被正确的 认出。
好了,以下的图示说明了脚本与 ASM 代码的关系:
脚本源代码 lock
faceplayer ……
脚 本 编 译 器
二进制脚本代码 6A 5A ……
脚 本 解 释 系 统
ASM 代码 Push r0 ……
脚本的工作原理
由此看来,直接编写 ASM 代码,其实质是凌驾于脚本之上的。或者 说,脚本的本质,就是不断地调用预先内臵的 ASM 代码群。
下面,就是从内存地址里面取值了。给 r0 的地址再增加一个 0xC, 恰好就能得到我们要的私有编号的地址。
现在把这个读到的私有编号存到变量 0x800D 当中。
以上就是这段代码的意义了。
那么,我们来看一下私有编号在内存当中究竟是什么结构吧:
[名字 (8 bytes)] [性别 (1 byte)] [??? (1 byte)] [训练师编号 (2 bytes)] [私有编号 (2 bytes)] [游戏时间(小时)(2 bytes)] [分钟 (1 byte)] [秒 (1 byte)] [帧 (1 byte)] [??? (1 byte)] [选项(2 bytes)]
这里特别要说一下,我们的 ASM 代码地址还要+1 才能用在 callasm 命令的后面。具体的原因后面我会说到。
把脚本随便给一个能说上话的 NPC,然后进入游戏对话,他就能告 诉你,你的训练师私有编号是多少了。
私有编号是什么?它是一个用来区分各个玩家的编号,以保证在你进 行通信时不会乱套。 通常,这个编号不会明文显示,脚本当中也没有直接读取显示的相关 命令,但通过 ASM 代码,我们让显示它成为了可能。